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 05:51

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
查看更多
Anthone
3楼-- · 2019-01-21 05:53

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")
查看更多
Emotional °昔
4楼-- · 2019-01-21 05:53

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楼-- · 2019-01-21 05:58

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
查看更多
ら.Afraid
6楼-- · 2019-01-21 06:00
  book = MiniTest::Mock.new
  book.expect :title, "War and Piece"

  Book.stub :new, book do
    wp = Book.new
    wp.title # => "War and Piece"
  end
查看更多
孤傲高冷的网名
7楼-- · 2019-01-21 06:03

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.

查看更多
登录 后发表回答