Module: Minitest::Assertions

Defined in:
lib/minitest/mock_expectations/assertions.rb

Overview

Provides method call assertions for minitest

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

Instance Method Summary collapse

Instance Method Details

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

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


60
61
62
63
64
65
66
67
68
# File 'lib/minitest/mock_expectations/assertions.rb', line 60

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

  object.stub(method_name, proc { times_called += 1; returns }) { yield }

  error = "Expected #{method_name} to be called #{times} times, but was called #{times_called} times"
  error = "#{message}.\n#{error}" if message
  assert_equal times, times_called, error
end

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

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


174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/minitest/mock_expectations/assertions.rb', line 174

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

  klass.define_method("stubbed_#{method_name}") do |*|
    times_called += 1

    returns
  end

  klass.alias_method("original_#{method_name}", method_name)
  klass.alias_method(method_name, "stubbed_#{method_name}")

  yield

  error = "Expected #{method_name} to be called #{times} times, but was called #{times_called} times"
  error = "#{message}.\n#{error}" if message

  assert_equal times, times_called, error
ensure
  klass.alias_method(method_name, "original_#{method_name}")
  klass.undef_method("original_#{method_name}")
  klass.undef_method("stubbed_#{method_name}")
end

#assert_called_with(object, method_name, arguments, returns: nil) ⇒ Object

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_with(@post, :add_comment, [[["Thanks for sharing this."]]]) do
  @post.add_comment(["Thanks for sharing this."])
end

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

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

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

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

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


128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/minitest/mock_expectations/assertions.rb', line 128

def assert_called_with(object, method_name, arguments, returns: nil)
  mock = Minitest::Mock.new

  if arguments.all? { |argument| argument.is_a?(Array) }
    arguments.each { |argument| mock.expect(:call, returns, argument) }
  else
    mock.expect(:call, returns, arguments)
  end

  object.stub(method_name, mock) { yield }

  mock.verify
end

#refute_called(object, method_name, message = nil, &block) ⇒ Object Also known as: assert_not_called

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

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


75
76
77
# File 'lib/minitest/mock_expectations/assertions.rb', line 75

def refute_called(object, method_name, message = nil, &block)
  assert_called(object, method_name, message, times: 0, &block)
end

#refute_called_on_instance_of(klass, method_name, message = nil, &block) ⇒ Object Also known as: assert_not_called_on_instance_of

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


211
212
213
# File 'lib/minitest/mock_expectations/assertions.rb', line 211

def refute_called_on_instance_of(klass, method_name, message = nil, &block)
  assert_called_on_instance_of(klass, method_name, message, times: 0, &block)
end