Hello!

A few months ago, utilum and me(bogdanvlviv) were working on removing Mocha from Rails. See our work in rails/rails#33162, and related pull requests https://github.com/rails/rails/pulls?q=Mocha. The main reason for removing this as mentioned in the Mocha’s README.md file is that “Mocha is currently not thread-safe”.

There were lots of Mocha’s mocks and stubs in the Rails’s tests, so we needed to replace all of them. We replaced some stub-objects with Ruby classes, Mocha’s #stubs method replaced with minitest’s #stub method, added new, and used already existed custom method call assertions.

I thought these “method call assertions” could be useful for the Ruby community, so I decided to create a gem that would extend minitest with these, almost the same, assertions. It is how gem minitest-mock_expectations was born.

NOTE: Module ActiveSupport::Testing::MethodCallAssertions is marked as private API by # :nodoc: comment.

minitest-mock_expectations 1.0.0

Provides method call assertions for minitest.

Installation

Add this line to your application’s Gemfile:

gem "minitest-mock_expectations"

And then execute:

$ bundle

Or install it yourself as:

$ gem install minitest-mock_expectations

Usage

require "minitest/mock_expectations"

Imagine we have model Post:

class Post
  attr_accessor :title, :body
  attr_reader :comments

  def initialize(title: "", body: "", comments: [])
    @title = title
    @body = body
    @comments = comments
  end

  def add_comment(comment)
    @comments << comment

    "Thank you!"
  end
end

And variable @post that reffers to instance of Post:

def setup
  @post = Post.new(
    title: "What is new in Rails 6.0",
    body: "https://bogdanvlviv.com/posts/ruby/rails/what-is-new-in-rails-6_0.html",
    comments: [
      "Looking really good.",
      "I really like this post."
    ]
  )
end

assert_called(object, method_name, message = nil, times: 1, returns: nil)

Asserts that the method will be called on the object in the block

assert_called(@post, :title) do
  @post.title
end

In order to assert that the method will be called multiple times on the object in the block set :times option:

assert_called(@post, :title, times: 2) do
  @post.title
  @post.title
end

You can stub the return value of the method in the block via :returns option:

assert_called(@post, :title, returns: "What is new in Rails 5.2") do
  assert_equal "What is new in Rails 5.2", @object.title
end

assert_equal "What is new in Rails 6.0", @object.title

refute_called(object, method_name, message = nil, &block)

Asserts that the method will not be called on the object in the block

refute_called(@post, :title) do
  @post.body
end

assert_not_called

Alias for refute_called.

assert_called_with(object, method_name, arguments, returns: nil)

Asserts that the method will be called with the arguments on the object in the block

assert_called_with(@post, :add_comment, ["Thanks for sharing this."]) do
  @post.add_comment("Thanks for sharing this.")
end

You can stub the return value of the method in the block via :returns option:

assert_called_with(@post, :add_comment, ["Thanks for sharing this."], returns: "Thanks!") do
  assert_equal "Thanks!", @post.add_comment("Thanks for sharing this.")
end

assert_equal "Thank you!", @post.add_comment("Thanks for sharing this.")

You can also assert that the method will be called with different arguments on the object in the block:

assert_called_with(@post, :add_comment, [["Thanks for sharing this."], ["Thanks!"]]) do
  @post.add_comment("Thanks for sharing this.")
  @post.add_comment("Thanks!")
end

assert_called_on_instance_of(klass, method_name, message = nil, times: 1, returns: nil)

Asserts that the method will be called on an instance of the klass in the block

assert_called_on_instance_of(Post, :title) do
  @post.title
end

In order to assert that the method will be called multiple times on an instance of the klass in the block set :times option:

assert_called_on_instance_of(Post, :title, times: 2) do
  @post.title
  @post.title
end

You can stub the return value of the method in the block via :returns option:

assert_called_on_instance_of(Post, :title, returns: "What is new in Rails 5.2") do
  assert_equal "What is new in Rails 5.2", @post.title
end

assert_equal "What is new in Rails 6.0", @post.title

Use nesting of the blocks in order assert that the several methods will be called on an instance of the klass in the block:

assert_called_on_instance_of(Post, :title, times: 3) do
  assert_called_on_instance_of(Post, :body, times: 2) do
    @post.title
    @post.body
    @post.title
    @post.body
    @post.title
  end
end

refute_called_on_instance_of(klass, method_name, message = nil, &block)

Asserts that the method will not be called on an instance of the klass in the block

refute_called_on_instance_of(Post, :title) do
  @post.body
end

Use nesting of the blocks in order assert that the several methods will not be called on an instance of the klass in the block:

refute_called_on_instance_of(Post, :title) do
  refute_called_on_instance_of(Post, :body) do
    @post.add_comment("Thanks for sharing this.")
  end
end

assert_not_called_on_instance_of

Alias for refute_called_on_instance_of.

References

GitHub: https://github.com/bogdanvlviv/minitest-mock_expectations.

RubyGems: https://rubygems.org/gems/minitest-mock_expectations.