Как я могу заглушить вещи в MiniTest?
В рамках моего теста я хочу заглушить законченный ответ для любого экземпляра класса.
Это может выглядеть примерно так:
Book.stubs(:title).any_instance().returns("War and Peace")
Тогда, когда я называю @book.title
, он возвращает "Война и мир".
Есть ли способ сделать это в MiniTest?
Если да, можете ли вы дать мне пример фрагмента кода?
Или мне нужно что-то вроде мокко?
MiniTest поддерживает Mocks, но Mocks - это излишество для того, что мне нужно.
Ответы
Ответ 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
Если вы заинтересованы в простом обрезании без насмешливой библиотеки, тогда достаточно легко сделать это в 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 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
Я использую minitest для тестирования моего Gems, но все мои заглушки с моккою, возможно, все можно будет сделать в мини-сериале с помощью Mocks (нет заглушек или чего-то еще, но макеты довольно мощные), но я нахожу мокка отлично справляется, если это помогает:
require 'mocha'
Books.any_instance.stubs(:title).returns("War and Peace")
Ответ 4
Вы можете легко заглушить методы в 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"
Ответ 5
Вы не можете сделать это с помощью Minitest. Однако вы можете заглушить какой-либо конкретный экземпляр:
book = Book.new
book.stub(:title, 'War and Peace') do
assert_equal 'War and Peace', book.title
end
Ответ 6
Просто для дальнейшего объяснения ответа @panic предположим, что у вас есть класс Book:
require 'minitest/mock'
class Book; end
Сначала создайте заглушку экземпляра Book и сделайте так, чтобы она возвращала желаемый заголовок (любое количество раз):
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-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: No more expects available
.
Кроме того, для любого конкретного экземпляра вызов метода MockExpectationError: expected title()
меньше раз, чем указано, вызывает MockExpectationError: expected title()
, но только в том случае, если вы вызываете #verify
для этого экземпляра в этой точке.
Ответ 7
Вы всегда можете создать модуль в своем тестовом коде и использовать с ним include или extension для классов или объектов 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
Это позволяет вам составить более сложное поведение, чем простая заглушка
Ответ 8
Я думал, что поделился бы примером, который я построил на ответах здесь.
Мне нужно было заглушить метод в конце длинной цепочки методов. Все началось с нового экземпляра оболочки 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
.