How do I stub things in MiniTest?

2019-01-21 05:13发布

Within my test I want to stub a canned response for any instance of a class.

It might look like something like:

Book.stubs(:title).any_instance().returns("War and Peace")

Then whenever I call @book.title it returns "War and Peace".

Is there a way to do this within MiniTest? If yes, can you give me an example code snippet?

Or do I need something like mocha?

MiniTest does support Mocks but Mocks are overkill for what I need.

8条回答
叛逆
2楼-- · 2019-01-21 06:12

Just to further explicate @panic's answer, let's assume you have a Book class:

require 'minitest/mock'
class Book; end

First, create a Book instance stub, and make it return your desired title (any number of times):

book_instance_stub = Minitest::Mock.new
def book_instance_stub.title
  desired_title = 'War and Peace'
  return_value = desired_title
  return_value
end

Then, make the Book class instantiate your Book instance stub (only and always, within the following code block):

return_value = book_instance_stub
method_to_redefine = :new
Book.stub method_to_redefine, return_value do
  ...

Within this code block (only), the Book::new method is stubbed. Let's try it:

  ...
  some_book = Book.new
  another_book = Book.new
  puts some_book.title #=> "War and Peace"
  puts some_book.title #=> "War and Peace"
end

Or, most tersely:

require 'minitest/mock'
class Book; end
instance = Minitest::Mock.new
def instance.title() 'War and Peace' end
Book.stub :new, instance do
  book = Book.new
  another_book = Book.new
  puts book.title #=> "War and Peace"
  puts book.title #=> "War and Peace"
end

Alternatively, you can install the Minitest extension gem 'minitest-stub_any_instance'. (Note: using this approach, the Book#title method must exist before you stub it.) Now, you can say more simply:

require 'minitest/stub_any_instance'
class Book; def title() end end
desired_title = 'War and Peace'
Book.stub_any_instance :title, desired_title do
  book = Book.new
  another_book = Book.new
  puts book.title #=> "War and Peace"
  puts book.title #=> "War and Peace"
end

If you want to verify that Book#title is invoked a certain number of times, then do:

require 'minitest/mock'
class Book; end

desired_title = 'War and Peace'
return_value = desired_title
method = :title
book_instance_stub = Minitest::Mock.new
number_of_title_invocations = 2
number_of_title_invocations.times do
  book_instance_stub.expect method, return_value
end

return_value = book_instance_stub
method_to_redefine = :new
Book.stub method_to_redefine, return_value do
  some_book = Book.new
  puts some_book.title #=> "War and Peace"
  puts some_book.title #=> "War and Peace"
end
book_instance_stub.verify

Thus, for any particular instance, invoking the stubbed method more times than specified raises MockExpectationError: No more expects available.

Also, for any particular instance, invoking the stubbed method fewer times than specified raises MockExpectationError: expected title(), but only if you invoke #verify afterward on that instance.

查看更多
做自己的国王
3楼-- · 2019-01-21 06:12

You can always create a module in your test code, and use include or extend to monkey-patch classes or objects with it. eg (in book_test.rb)

module BookStub
  def title
     "War and Peace"
  end
end

Now you can use it in your tests

describe 'Book' do
  #change title for all books
  before do
    Book.include BookStub
  end
end

 #or use it in an individual instance
 it 'must be War and Peace' do
   b=Book.new
   b.extend BookStub
   b.title.must_equal 'War and Peace'
 end

This allows you to put together more complex behaviours than a simple stub might allow

查看更多
登录 后发表回答