我有以下型号
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似乎忽略了这一点。有可能吗?
答案 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