如何使用factory_girl模拟和存储活动记录before_create回调

时间:2011-09-25 05:12:32

标签: ruby-on-rails-3 activerecord mocking rspec-rails factory-bot

我有一个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 mock和stub帮助程序加载到我的factories.rb中,这可能不是处理此问题的最佳方法。

6 个答案:

答案 0 :(得分:19)

作为VCR宝石的作者,你可能希望我推荐它用于这样的情况。我的确推荐它用于测试依赖于HTTP的代码,但我认为你的设计存在潜在的问题。不要忘记TDD(测试驱动开发)是一个设计学科,当你发现轻松测试某些东西很痛苦时,它会告诉你一些关于你的设计的东西。听听你的考试的痛苦!

在这种情况下,我认为您的模型没有进行第三方API调用的业务。这是对单一责任原则的严重违反。模型应该负责某些数据的验证和持久性,但这肯定超出了这个范围。

相反,我建议您将第三方API调用移动到观察者。 Pat Maddox有一个great blog post讨论观察者如何(而且应该)在不违反SRP(单一责任原则)的情况下松散地结合事物,以及如何使测试变得更加容易,并且还可以改善您的设计。

一旦将其移动到观察者中,在单元测试中禁用观察者就很容易(除了该观察者的特定测试),但在生产和集成测试中保持启用。您可以使用Pat的no-peeping-toms插件来帮助解决此问题,或者,如果您使用的是rails 3.1,则应该查看ActiveModel中内置的new functionality,以便{J 3}}。< / p>

答案 1 :(得分:2)

查看VCR gem(https://www.relishapp.com/myronmarston/vcr)。它将记录您的测试套件的HTTP交互并为您播放它们。删除任何实际建立到第三方API的HTTP连接的要求。我发现这比手动模拟交互更简单。以下是使用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

答案 2 :(得分:1)

虽然我可以看到封装方面的吸引力,但第三方存根不一定要在你的工厂内发生(在某些方面也许不应该发生)。

您可以在RSpec测试开始时简单地定义它,而不是将其封装在工厂中。这样做还可以确保测试的假设清晰并在开始时说明(调试时非常有用)

在使用PricePlan的任何测试之前,设置所需的响应,然后从第三方.create方法返回它:

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

这应该允许你调用方法但是会停止远程调用。

*看起来你的第三方存根对原始对象有一些依赖关系(:price_in_cents)但是我不知道更多关于确切的依赖关系我无法猜出什么是合适的存根(或者如果有必要的话)* < / p>

答案 3 :(得分:0)

FactoryGirl可以存根对象的属性,也许可以帮助你:

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

您可以在FactoryGirl's rdocs

中找到更多信息

答案 4 :(得分:0)

我有同样的问题。除了观察者讨论(它可能是正确的方法),这对我有用(这是一个开始,可以/应该改进):

使用以下内容将文件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 }

答案 5 :(得分:-1)

嗯,首先,你是对的'模拟和存根'不是工厂女孩的语言

猜测您的模型关系,我认为您将要构建另一个对象工厂,设置其属性,然后关联它们。

#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

希望我没有非法破坏这种关系。