How do I stub things in MiniTest?

青春壹個敷衍的年華 提交于 2019-12-17 17:31:47

问题


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.


回答1:


  # Create a mock object
  book = MiniTest::Mock.new
  # Set the mock to expect :title, return "War and Piece"
  # (note that unless we call book.verify, minitest will
  # not check that :title was called)
  book.expect :title, "War and Piece"

  # Stub Book.new to return the mock object
  # (only within the scope of the block)
  Book.stub :new, book do
    wp = Book.new # returns the mock object
    wp.title      # => "War and Piece"
  end



回答2:


If you're interesting in simple stubbing without a mocking library, then it's easy enough to do this in Ruby:

class Book
  def avg_word_count_per_page
    arr = word_counts_per_page
    sum = arr.inject(0) { |s,n| s += n }
    len = arr.size
    sum.to_f / len
  end

  def word_counts_per_page
    # ... perhaps this is super time-consuming ...
  end
end

describe Book do
  describe '#avg_word_count_per_page' do
    it "returns the right thing" do
      book = Book.new
      # a stub is just a redefinition of the method, nothing more
      def book.word_counts_per_page; [1, 3, 5, 4, 8]; end
      book.avg_word_count_per_page.must_equal 4.2
    end
  end
end

If you want something more complicated like stubbing all instances of a class, then it is also easy enough to do, you just have to get a little creative:

class Book
  def self.find_all_short_and_unread
    repo = BookRepository.new
    repo.find_all_short_and_unread
  end
end

describe Book do
  describe '.find_all_short_unread' do
    before do
      # exploit Ruby's constant lookup mechanism
      # when BookRepository is referenced in Book.find_all_short_and_unread
      # then this class will be used instead of the real BookRepository
      Book.send(:const_set, BookRepository, fake_book_repository_class)
    end

    after do
      # clean up after ourselves so future tests will not be affected
      Book.send(:remove_const, :BookRepository)
    end

    let(:fake_book_repository_class) do
      Class.new(BookRepository)
    end

    it "returns the right thing" do 
      # Stub #initialize instead of .new so we have access to the
      # BookRepository instance
      fake_book_repository_class.send(:define_method, :initialize) do
        super
        def self.find_all_short_and_unread; [:book1, :book2]; end
      end
      Book.find_all_short_and_unread.must_equal [:book1, :book2]
    end
  end
end



回答3:


I use minitest for all my Gems testing, but do all my stubs with mocha, it might be possible to do all in minitest with Mocks(there is no stubs or anything else, but mocks are pretty powerful), but I find mocha does a great job, if it helps:

require 'mocha'    
Books.any_instance.stubs(:title).returns("War and Peace")



回答4:


You can easily stub methods in MiniTest. The information is available at github.

So, following your example, and using the Minitest::Spec style, this is how you should stub methods:

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

# - MiniTest - #
Book.stub :title, "War and Peace" do
  book = Book.new
  book.title.must_equal "War and Peace"
end

This a really stupid example but at least gives you a clue on how to do what you want to do. I tried this using MiniTest v2.5.1 which is the bundled version that comes with Ruby 1.9 and it seems like in this version the #stub method was not yet supported, but then I tried with MiniTest v3.0 and it worked like a charm.

Good luck and congratulations on using MiniTest!

Edit: There is also another approach for this, and even though it seems a little bit hackish, it is still a solution to your problem:

klass = Class.new Book do
  define_method(:title) { "War and Peace" }
end

klass.new.title.must_equal "War and Peace"



回答5:


You cannot do this with Minitest. However, you can stub any particular instance:

book = Book.new
book.stub(:title, 'War and Peace') do
  assert_equal 'War and Peace', book.title
end



回答6:


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):

method_to_redefine = :new
return_value = book_instance_stub
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"
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"
end

Alternatively, you can install the Minitest extension gem minitest-stub_any_instance. (Note: when 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"
end

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

require 'minitest/mock'
class Book; end

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

method_to_redefine = :new
return_value = book_instance_stub
Book.stub method_to_redefine, return_value do
  some_book = Book.new
  puts some_book.title #=> "War and Peace"
# And again:
  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, having invoked the stubbed method fewer times than specified raises MockExpectationError: expected title(), but only if you invoke #verify on that instance at that point.




回答7:


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




回答8:


I thought I'd share an example that I built upon the answers here.

I needed to stub a method at the end of a long chain of methods. It all started with a new instance of a PayPal API wrapper. The call I needed to stub was essentially:

paypal_api = PayPal::API.new
response = paypal_api.make_payment
response.entries[0].details.payment.amount

I created a class that returned itself unless the method was amount:

paypal_api = Class.new.tap do |c|
  def c.method_missing(method, *_)
    method == :amount ? 1.25 : self
  end
end

Then I stubbed it in to PayPal::API:

PayPal::API.stub :new, paypal_api do
  get '/paypal_payment', amount: 1.25
  assert_equal 1.25, payments.last.amount
end

You could make this work for more than just one method by making a hash and returning hash.key?(method) ? hash[method] : self.



来源:https://stackoverflow.com/questions/7211086/how-do-i-stub-things-in-minitest

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!