Как обманывать и блокировать активную запись before_create callback с помощью factory_girl
У меня есть модель ActiveRecord, PricePackage. У этого есть вызов before_create. Этот обратный вызов использует сторонний API для удаленного подключения. Я использую factory девушку и хотел бы закрыть эту апи, чтобы при создании новых фабрик во время тестирования удаленные вызовы не выполнялись.
Я использую Rspec для mocks и stubs. Проблема, с которой я сталкиваюсь, заключается в том, что методы Rspec недоступны на моих фабриках. Rb
Модель:
class PricePackage < ActiveRecord::Base
has_many :users
before_create :register_with_3rdparty
attr_accessible :price, :price_in_dollars, :price_in_cents, :title
def register_with_3rdparty
return true if self.price.nil?
begin
3rdPartyClass::Plan.create(
:amount => self.price_in_cents,
:interval => 'month',
:name => "#{::Rails.env} Item #{self.title}",
:currency => 'usd',
:id => self.title)
rescue Exception => ex
puts "stripe exception #{self.title} #{ex}, using existing price"
plan = 3rdPartyClass::Plan.retrieve(self.title)
self.price_in_cents = plan.amount
return true
end
end
factory:
#PricePackage
Factory.define :price_package do |f|
f.title "test_package"
f.price_in_cents "500"
f.max_domains "20"
f.max_users "4"
f.max_apps "10"
f.after_build do |pp|
#
#heres where would like to mock out the 3rd party response
#
3rd_party = mock()
3rd_party.stub!(:amount).price_in_cents
3rdPartyClass::Plan.stub!(:create).and_return(3rd_party)
end
end
Я не уверен, как получить rspec mock и помощников-заглушек, загруженных в мои фабрики .rb, и это может быть не лучший способ справиться с этим.
Ответы
Ответ 1
Как автор драгоценного камня VCR, вы, вероятно, ожидаете, что я рекомендую его для подобных случаев. Я действительно рекомендую его для тестирования HTTP-зависимого кода, но я думаю, что есть основная проблема с вашим дизайном. Не забывайте, что TDD (разработка, основанная на тестах) призвана стать дисциплиной дизайна, и когда вам будет легко испытать что-то, что говорит вам что-то о вашем дизайне. Слушайте боль в тестах!
В этом случае, я думаю, ваша модель не имеет бизнеса, делающего вызов сторонних API. Это довольно значительное нарушение принципа единой ответственности. Модели должны отвечать за проверку и сохранение некоторых данных, но это определенно выше этого.
Вместо этого я бы рекомендовал вам переместить вызов стороннего API в наблюдателя. У Пэта Мэддокса есть отличный пост в блоге, в котором обсуждается, как наблюдатели могут (и должны) использоваться, чтобы свободно сочетать вещи, не нарушая SRP (принцип единой ответственности) и как это делает тестирование, намного, намного проще, а также улучшает ваш дизайн.
Как только вы переместили это в наблюдателя, достаточно легко отключить наблюдателя в своих модульных тестах (за исключением конкретных тестов для этого наблюдателя), но сохранить его в процессе производства и в тестах интеграции. Вы можете использовать плагин Pat no-peeping-toms, чтобы помочь в этом, или, если вы находитесь на рейках 3.1, вы должны проверить новая функциональность, встроенная в ActiveModel, которая позволяет легко включать/отключать наблюдателей.
Ответ 2
Оформить заказ на видеомагнитофон (https://www.relishapp.com/myronmarston/vcr). Он будет записывать ваши тестовые пакеты HTTP-взаимодействия и воспроизводить их для вас. Удаление любых требований для фактического подключения HTTP к сторонним API. Я считаю, что это гораздо более простой подход, чем издевательствование взаимодействия вручную. Вот пример использования библиотеки Foursquare.
VCR.config do |c|
c.cassette_library_dir = 'test/cassettes'
c.stub_with :faraday
end
describe Checkin do
it 'must check you in to a location' do
VCR.use_cassette('foursquare_checkin') do
Skittles.checkin('abcd1234') # Doesn't actually make any HTTP calls.
# Just plays back the foursquare_checkin VCR
# cassette.
end
end
end
Ответ 3
Хотя я вижу апелляцию с точки зрения инкапсуляции, третья сторона не должна произойти (и в некотором роде, возможно, не должна произойти) в вашем factory.
Вместо того, чтобы инкапсулировать его в factory, вы можете просто определить его в начале ваших тестов RSpec. Это также гарантирует, что допущения ваших тестов ясны и указаны в начале (что может быть очень полезно при отладке)
Перед любыми тестами, которые используют PricePlan, настройте желаемый ответ, а затем верните его из стороннего метода .create
:
before(:all) do
3rd_party = mock('ThirdParty')
3rdPartyClass::Plan.stub(:create).and_return(true)
end
Это должно позволить вам вызвать метод, но не удастся удалить удаленный вызов.
* Похоже, что ваш сторонний заглушка имеет некоторые зависимости от исходного объекта (: price_in_cents), однако, не зная больше о точной зависимости, я не могу догадаться, что было бы подходящим stubbing (или если это необходимо) *
Ответ 4
FactoryGirl может заглушить атрибуты объекта, возможно, это может помочь вам:
# Returns an object with all defined attributes stubbed out
stub = FactoryGirl.build_stubbed(:user)
Дополнительную информацию можно найти в FactoryGirl rdocs
Ответ 5
У меня была такая же точная проблема. Обсуждение наблюдателя в стороне (это может быть правильный подход), вот что сработало для меня (это начало и может/должно быть улучшено):
добавить файл 3rdparty.rb в спецификацию/поддержку с помощью этого содержимого:
RSpec.configure do |config|
config.before do
stub(3rdPartyClass::Plan).create do
[add stuff here]
end
end
end
И убедитесь, что ваш spec_helper.rb имеет следующее:
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
Ответ 6
Хорошо, во-первых, вы правы, что "mock and stub" не являются языком Factory Girl
Угадав ваши отношения с моделью, я думаю, вы захотите создать еще один объект factory, установить его свойства и затем связать их.
#PricePackage
Factory.define :price_package do |f|
f.title "test_package"
f.price_in_cents "500"
f.max_domains "20"
f.max_users "4"
f.max_apps "10"
f.after_build do |pp|
f.3rdClass { Factory(:3rd_party) }
end
Factory.define :3rd_party do |tp|
tp.price_in_cents = 1000
end
Надеюсь, я не исказил отношения неразборчиво.