使用Fabrication时从构造函数调用的Stub方法

时间:2012-01-16 13:35:49

标签: ruby-on-rails-3 rspec stub fabrication-gem

我有以下型号

class User < ActiveRecord::Base
  before_create :set_some_values

  private
  def set_some_values
    #do something
  end
end

在规范中,我使用Fabrication gem来创建对象,但我找不到一种方法来存根set_some_values方法。我试过了

User.any_instance.stub!(:set_some_values).and_return(nil)

但Fabrication似乎忽略了这一点。有可能吗?

2 个答案:

答案 0 :(得分:2)

这就是为什么我不喜欢ActiveRecord回调 - 因为如果你想要与回调无关(因为,比如你在回调中调用外部服务)你仍然需要关心剔除它。是的,你可以在回调中找出方法,但这是同样的问题,实际上它有点糟糕,因为现在你担心里面一个你想要的方法没什么用。

像往常一样,这里有多种选择。

我过去经常使用的一个选项是,为你的回调添加一个条件,默认情况下将其关闭。所以你的Post类看起来像:

class Post
  before_save :sync_with_store, :if => :syncing_with_store?

  def syncing_with_store?; @syncing_with_store; end
  attr_writer :syncing_with_store

  def sync_with_store
     # make an HTTP request or something
  end
end

现在无论你想在哪里打电话回叫(可能在你的控制器或任何地方),你都可以在致电post.syncing_with_store = true之前设置post.save

这种方法的缺点是,你(和其他开发人员一起工作)必须记住这一点,并且你必须这样做并不是很明显。另一方面,如果你忘记这样做,没有什么不好的事情发生。

另一种选择是使用假类。假设您有一个Post在保存时将其数据推送到外部数据存储。您可以将推送的代码提取到可以在Post.pusher_service访问的单独类(例如Pusher)。但是,默认情况下,这将被设置为假的Pusher类,它响应相同的接口但什么都不做。所以喜欢:

class Post
  class << self
    attr_accessor :pusher_service
  end
  self.pusher_service = FakePostPusher

  before_save :sync_with_store

  def sync_with_store
    self.class.pusher_service.run(self)
  end
end

class FakePostPusher
  def self.run(post)
    new(post).run
  end

  def initialize(post)
    @post = post
  end

  def run
    # do nothing
  end
end

class PostPusher < FakePostPusher
  def run
    # actually make the HTTP request or whatever
  end
end

在生产环境文件中,您需要设置Post.pusher_service = Pusher。在单个测试或测试用例中,您将创建Post的子类 - let(:klass) { Class.new(Post) } - 并设置klass.pusher_service = Pusher(这样您就不会永久设置它并影响将来的测试)。

我一直在尝试的第三种方法是:只是不要使用ActiveRecord回调。这是我从Gary Bernhardt's screencasts获取的东西(顺便说一句,这是非常了不起的)。相反,定义一个包装创建帖子的服务类。类似的东西:

class PostCreator
  def self.run(attrs={})
    new(attrs).run
  end

  def initialize(attrs={})
    @post = Post.new(attrs)
  end

  def run
    if @post.save
      make_http_request
      return true
    else
      return false
    end
  end

  def make_http_request
    # ...
  end
end

这种方式PostCreator.run(attrs)是创建帖子而不是通过Post的事实上的方式。现在要在Post中测试保存,不需要存根回调。如果你想测试PostCreator过程,那就没有什么神奇之处,你可以轻松地找出你想要的任何方法或者单独测试它们。 (你可以说这里剔除方法与剔除AR回调相同,但我认为它更明确地发生了什么。)显然这只会处理帖子创建,但是你也可以对帖子更新做同样的事情。

无论如何,不​​同的想法,挑选你的毒药。

答案 1 :(得分:0)

当你在记录上调用#save时,会调用#set_some_values方法。因此它与构造函数无关,因此您不需要存根User.any_instance - 只需创建您的记录然后执行部分存根,如:

record.stub(:set_some_values)
record.save