Как заглушить что-нибудь в MiniTest?

В моем тесте я хочу заглушить шаблонный ответ для любого экземпляра класса.

Это может выглядеть примерно так:

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

Затем всякий раз, когда я вызываю @book.title, он возвращает «Война и мир».

Есть ли способ сделать это в MiniTest? Если да, не могли бы вы дать мне пример фрагмента кода?

Или мне нужно что-то вроде мокко?

MiniTest поддерживает Mocks, но Mocks - это излишек для того, что мне нужно.


person Nick    schedule 26.08.2011    source источник


Ответы (8)


Я использую minitest для всех своих тестов Gems, но все мои заглушки делаю с помощью mocha, возможно, можно будет сделать все в minitest с Mocks (нет заглушек или чего-то еще, но mocks довольно мощные), но я считаю, что mocha делает отличная работа, если поможет:

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

Если вам интересны простые заглушки без фиктивной библиотеки, то сделать это в 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

Если вы хотите что-то более сложное, например, заглушить все экземпляры класса, это тоже достаточно просто сделать, вам просто нужно проявить немного творчества:

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
person Elliot Winkler    schedule 26.04.2012
comment
если вы переопределите метод в тесте, он вернется, как только тест будет завершен, или останется переопределенным для других тестов? - person Toby 1 Kenobi; 14.05.2018

Вы можете легко заглушить методы класса в MiniTest. Информация доступна на github.

Итак, следуя вашему примеру и используя стиль Minitest::Spec, вы должны использовать методы-заглушки следующим образом:

# - 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

Это действительно глупый пример, но, по крайней мере, он дает вам представление о том, как делать то, что вы хотите делать. Я пробовал это с помощью MiniTest v2.5.1, который является связанной версией, которая поставляется с Ruby 1.9, и похоже, что в этой версии метод #stub еще не поддерживался, но затем Я попробовал использовать MiniTest v3.0, и он отлично сработал.

Удачи и поздравляем с использованием MiniTest!

Изменить: для этого есть еще один подход, и хотя он кажется немного хакерским, он все же является решением вашей проблемы:

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

klass.new.title.must_equal "War and Peace"
person mongrelion    schedule 24.05.2012
comment
Похоже, Book.stub :title, "War and Peace" работает, только если title является class_method Book. Я не могу воспроизвести то же поведение, что и any_instance, ошибка NameError: undefined method title 'для Book' - person fguillen; 29.08.2012
comment
undefined название метода "для книги" - person qichunren; 01.02.2013
comment
stub работает с metaclass = class << self; self; end в соответствии с источником фиктивной библиотеки в MiniTest. Таким образом, кажется, что вы можете заглушить только одноэлементные методы; В противном случае используйте вместо этого полный фиктивный объект. - person Michael De Silva; 03.02.2013
comment
Я получаю ошибку имени при использовании этого в моем приложении Rails. Я добавляю require 'minitest/mock', но все равно получаю NoMethodError: undefined method заглушку '' - person Tom Rossi; 20.07.2013
comment
@TomRossi, взгляните на это: github.com/seattlerb/minitest/issues/384 версия minitest, поставляемая с 1.9.3, не включает заглушку. Вам необходимо загрузить более позднюю версию minitest / mock - person ReggieB; 24.01.2014
comment
вы можете использовать Book.stub_any_instance вместо Book.stub, чтобы заглушить метод экземпляра. - person Jesse Mignac; 06.11.2015
comment
После gem install minitest-stub_any_instance и require 'minitest/stub_any_instance' можно делать Book::stub_any_instance {method}, {value}. Метод экземпляра класса Book уже должен существовать. - person MarkDBlackwell; 25.06.2016

Чтобы еще больше пояснить ответ @ panic, предположим, что у вас есть класс Book:

require 'minitest/mock'
class Book; end

Сначала создайте заглушку экземпляра книги и заставьте ее возвращать желаемый заголовок (любое количество раз):

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

Затем заставьте класс Book создать экземпляр вашей заглушки экземпляра Book (всегда и только в следующем блоке кода):

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

В этом блоке кода (только) метод Book::new заглушен. Давай попробуем:

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

Или, наиболее кратко:

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

В качестве альтернативы вы можете установить гем расширения Minitest minitest-stub_any_instance. (Примечание: при использовании этого подхода метод Book#title должен существовать до того, как вы его заглушите.) Теперь вы можете сказать проще:

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

Если вы хотите убедиться, что Book#title вызывается определенное количество раз, выполните:

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

Таким образом, для любого конкретного экземпляра вызов заглушенного метода больше раз, чем указано, вызывает MockExpectationError: No more expects available.

Кроме того, для любого конкретного экземпляра вызов заглушенного метода меньше раз, чем указано, вызывает MockExpectationError: expected title(), но только если вы вызываете #verify в этом экземпляре в этот момент.

person MarkDBlackwell    schedule 23.06.2016

Вы не можете заглушить все экземпляры класса, но вы можете заглушить любой метод экземпляра данного объекта следующим образом:

require "minitest/mock"

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

Вы всегда можете создать модуль в своем тестовом коде и использовать его для включения или расширения для классов или объектов monkey-patch. например (в book_test.rb)

module BookStub
  def title
     "War and Peace"
  end
end

Теперь вы можете использовать его в своих тестах

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

Это позволяет вам создавать более сложные модели поведения, чем может позволить простая заглушка.

person Steve Gooberman-Hill    schedule 28.07.2015
comment
Тесты должны отменять любые заглушки в конце, чтобы они не загрязняли другие тесты. Но в core ruby ​​ нет возможности отключить / отменить расширение модуля. . Есть несколько собственных расширений, например. github.com/rosylilly/uninclude для МРТ. - person Beni Cherniavsky-Paskin; 05.12.2018

Я подумал, что приведу пример, который я построил на ответах здесь.

Мне нужно было вставить метод в конец длинной цепочки методов. Все началось с нового экземпляра оболочки API PayPal. Звонок, который мне нужно было заглушить, был по существу:

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

Я создал класс, который возвращал себя, если только метод не был amount:

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

Затем я вставил его в PayPal::API:

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

Вы можете заставить эту работу работать не только для одного метода, создав хеш и вернув hash.key?(method) ? hash[method] : self.

person Eric Boehs    schedule 11.03.2016

person    schedule
comment
Мне это действительно трудно читать. Намерение неясно .. Хотя это скорее комментарий к minitest, чем ваш ответ. - person Steven Soroka; 09.04.2014
comment
@StevenSoroka, здесь, в моем собственном ответе я подробно описываю ответ паники. - person MarkDBlackwell; 23.06.2016
comment
@StevenSoroka То же самое. Я добавил комментарии, чтобы сделать его более понятным для людей, не привыкших к синтаксису Minitest. - person David Moles; 14.06.2019