Как смоделировать и заглушить активную запись before_create callback с factory_girl - PullRequest
16 голосов
/ 25 сентября 2011

У меня есть модель ActiveRecord, PricePackage. Это обратный вызов before_create. Этот обратный вызов использует сторонний API для удаленного подключения. Я использую фабричную девчонку и хотел бы заглушить этот API, чтобы при создании новых фабрик во время тестирования не производились удаленные вызовы.

Я использую Rspec для издевательств и заглушек. У меня проблема в том, что методы 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

завод:

#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 в мой factory.rb, и это может быть не лучшим способом справиться с этим.

Ответы [ 6 ]

19 голосов
/ 22 октября 2011

Как автор камня VCR, вы, вероятно, ожидаете, что я порекомендую его для подобных случаев.Я действительно рекомендую его для тестирования HTTP-зависимого кода, но я думаю, что есть основная проблема с вашим дизайном.Не забывайте, что TDD (разработка через тестирование) призвана стать дисциплиной проектирования, и когда вам сложно испытать что-либо, это говорит вам о вашем дизайне.Прислушайтесь к боли ваших тестов!

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

Вместо этого я бы порекомендовал вам перенести сторонний вызов API в наблюдателя.У Пэта Мэддокса есть отличное сообщение в блоге , в котором обсуждается, как можно (и нужно) использовать наблюдателей, чтобы свободно соединять вещи, не нарушая SRP (принцип единой ответственности), и как это делает тестирование намного, намного проще, а такжеулучшает ваш дизайн.

Как только вы переместили это в наблюдателя, достаточно просто отключить наблюдателя в ваших модульных тестах (за исключением специальных тестов для этого наблюдателя), но оставить его включенным в производстве и вваши интеграционные тесты.Вы можете использовать плагин Пата no-peeping-toms , чтобы помочь с этим, или, если вы на рельсах 3.1, вы должны проверить новую функциональность , встроенную в ActiveModel, которая позволяетВы легко включаете / отключаете наблюдателей .

2 голосов
/ 08 октября 2011

Оформить заказ на видеомагнитофон (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
1 голос
/ 10 октября 2011

Хотя я вижу привлекательность с точки зрения инкапсуляции, сторонняя заглушка не должна происходить (а в некоторых случаях, возможно, и не должна происходить) на вашей фабрике.

Вместо того, чтобы инкапсулировать его на заводе, вы можете просто определить его в начале ваших тестов RSpec. Это также гарантирует, что допущения ваших тестов ясны и сформулированы в начале (что может быть очень полезно при отладке)

Перед любыми тестами, использующими PricePlan, настройте желаемый ответ и затем верните его стороннему методу .create:

before(:all) do
  3rd_party = mock('ThirdParty')
  3rdPartyClass::Plan.stub(:create).and_return(true)
end  

Это должно позволить вам вызвать метод, но отключит удаленный вызов.

* Похоже, что у вашей сторонней заглушки есть некоторые зависимости от исходного объекта (: price_in_cents), однако, не зная больше о точной зависимости, я не могу догадаться, что будет подходящей заглушкой (или, если таковая потребуется) *

0 голосов
/ 31 января 2012

У меня была точно такая же проблема.Помимо обсуждения с наблюдателем (это может быть правильный подход), вот что сработало для меня (это начало и его можно / нужно улучшить):

добавьте файл 3rdparty.rb вspec / support со следующим содержимым:

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 }
0 голосов
/ 09 октября 2011

FactoryGirl может заглушить атрибуты объекта, может быть, это поможет вам:

# Returns an object with all defined attributes stubbed out
stub = FactoryGirl.build_stubbed(:user)

Более подробную информацию вы можете найти в rdocs FactoryGirl

0 голосов
/ 27 сентября 2011

Ну, во-первых, вы правы, что «издевательство» - это не язык Factory Girl

Догадываясь о ваших модельных отношениях, я думаю, вы захотите построить другую фабрику объектов, установить ее свойства и затем связать их.

#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

Надеюсь, я не испортил отношения неразборчиво.

...