1

我有以下型号

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似乎忽略了这一点。有可能吗?

4

2 回答 2

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,它在保存时将其数据推送到外部数据存储。您可以提取执行推送到单独的类(例如 Pusher)的代码,该类可在Post.pusher_service. 但是,默认情况下,这将被设置为一个虚假的 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 的截屏视频中获得的(顺便说一句,这非常棒)。相反,定义一个服务类来包装创建帖子的行为。就像是:

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 回调是一样的,但我认为它更明确地发生了什么事。)显然这只处理帖子创建,但你也可以对帖子更新做同样的事情。

反正想法不一样,挑你的毒。

于 2012-05-17T17:36:38.967 回答
0

这里的#set_some_values 方法是在你对记录调用#save 时调用的。所以它与构造函数无关,因此您不需要存根 User.any_instance - 只需制作记录然后执行部分存根,如下所示:

record.stub(:set_some_values)
record.save
于 2012-05-15T17:03:27.727 回答