【问题标题】:Stub method called from constructor when using Fabrication使用 Fabrication 时从构造函数调用的存根方法
【发布时间】:2012-01-16 13:35:49
【问题描述】:

我有以下型号

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

【问题讨论】:

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


    【解决方案1】:

    这就是我不喜欢 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.save 之前设置post.syncing_with_store = true。

    这种方法的缺点是,您(以及与您一起工作的其他开发人员)必须牢记这一点,而且您必须这样做并不是很明显。另一方面,如果您忘记执行此操作,则不会发生任何不好的事情。

    另一种选择是使用假类。假设您有一个 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'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 中测试保存,无需存根回调。如果您想测试 PostCreator 过程,没有什么神奇的,您可以轻松地删除您想要的任何方法或独立测试它们。 (你可能会争辩说,在这里存根方法与存根 AR 回调是一样的,但我认为它更明确地发生了什么。)显然这只处理帖子创建,但你也可以对帖子更新做同样的事情。

    不管怎样,不同的想法,选择你的毒药。

    【讨论】:

    • 感谢您提供如此详细的概述。并且为 Gary Bernhardt 的截屏视频 +1,它们确实很棒。
    【解决方案2】:

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

    record.stub(:set_some_values)
    record.save
    

    【讨论】:

    • 但是我必须做Fabricate.build,然后存根并保存而不是仅仅创建。
    • 好的,你想在这里测试什么?是不是测试与#set_some_values无关,所以你只需要创建一个用户并存根#set_some_values的副作用?
    • 是的。我真的不在乎这里的方法本身,只需要把它拿开。 P.S. 好吧,现在我想我可以存根 set_some_values 正在使用的对象。
    猜你喜欢
    • 2013-08-10
    • 1970-01-01
    • 2010-09-23
    • 2013-01-02
    • 2016-05-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-08
    相关资源
    最近更新 更多