【问题标题】:How to mock any_instance in a scope with RSpec?如何使用 RSpec 在范围内模拟 any_instance?
【发布时间】:2013-02-28 18:20:28
【问题描述】:

我正在尝试编写一个规范,该规范期望在范围内的所有实例上调用一个方法。找不到优雅的方法。

这是我的代码的简化表示:

class MyClass < ActiveRecord::Base

scope :active, where(:status => 'active')
scope :inactive, where(:status => 'inactive')

def some_action
  # some code
end

这个类被另一个在MyClass.all上调用some_action的类使用:

class OtherClass

def other_method
  MyClass.all.each do |item|
    item.some_action
  end
end

我想改成:

class OtherClass

def other_method
  MyClass.active.each do |item|
    item.some_action
  end
end

要测试这种行为,我可以简单地 MyClass.stub(:active),返回一个存根数组,并在每个存根上期望 some_action。但我不喜欢这种方法,因为它暴露了太多的实现细节。

我认为会更优雅一点的是any_instance_in_scope。然后我可以简单地将我的规范写成:

MyClass.any_instance_in_scope(:active).should_receive(:some_action)

有什么办法可以做到吗?

【问题讨论】:

    标签: ruby-on-rails ruby rspec mocking bdd


    【解决方案1】:

    首先,MyClass.all.some_action 不起作用,因为MyClass#some_action 是一个实例方法,同时MyClass#all 返回一个Array -- 所以当你这样做时MyClass.all.some_action 你实际上是在调用@987654328 @。

    另外,请注意 MyClass.allMyClass.active 返回不同的类:

    MyClass.active.class # => ActiveRecord::Relation
    MyClass.active.all.class # => Array
    

    我不确定您的 some_action 应该做什么...我想您可能想要做的一些选择:

    选项 #1:缩小数据库查询范围

    如果some_action 正在过滤数组,则应将其转换为另一个作用域,如下所示:

    class MyClass < ActiveRecord::Base
      scope :active, where(:status => 'active')
      scope :inactive, where(:status => 'inactive')
      scope :some_action, ->(color_name) { where(color: color_name) }
    end
    

    然后使用MyClass.active.some_action('red').all 调用它。如果你只想要第一个结果,MyClass.active.some_action('red').first

    如何使用 RSpec 测试 scope

    这是一个很好的答案(以及原因):Testing named scopes with RSpec

    选项 #2:在实例之上执行操作

    假设您真的希望将MyClass#some_action 定义为实例方法。然后,您可以尝试这样做:

    class MyClass < ActiveRecord::Base
      scope :active, where(status: 'active')
      scope :inactive, where(status: 'inactive')
    
      def some_action
        self.foo = 'bar'
        self
      end
    end
    

    在这种情况下,您可以使用MyClass.active.last.some_action 执行它,因为#last 将返回一个实例,而不是整个数组。

    如何使用 RSpec 测试some_action

    我认为你应该简单地用期望来测试它:

    MyClass.should_receive(:some_action).at_least(:once)
    MyClass.active.last.some_action
    

    对此的补充讨论:How to say any_instance should_receive any number of times in RSpec

    选项 #3:集体行动

    假设您真的想运行MyClass.active.some_action。我建议你先试试这个(与选项 #2 相同的例子):

    class MyClass < ActiveRecord::Base
      scope :active, where(status: 'active')
      scope :inactive, where(status: 'inactive')
    
      def some_action
        self.foo = 'bar'
        self
      end
    end
    

    然后使用MyClass.active.all.map{|my_class| my_class.some_action } 运行。

    现在,如果您真的想要实现 MyClass.active.some_action -- 您希望在 ActiveRecord::Relation 的所有实例上执行 some_action我不建议这样做),这样做:

    class MyClass < ActiveRecord::Base
      scope :active, where(status: 'active')
      scope :inactive, where(status: 'inactive')
    
      def some_action
        # really do it
      end
    end
    

    还有……

    class ActiveRecord::Relation
      # run some_action over all instances
      def some_action
        to_a.each {|object| object.some_action }.tap { reset }
      end
    end
    

    再次,我不建议这样做

    如何使用 RSpec 测试 some_action

    与选项 #2 相同的情况:

    MyClass.should_receive(:some_action).at_least(:once)
    MyClass.active.last.some_action
    

    注意:所有代码都使用 Ruby 2.0.0-p0。安装使用,很好玩! :-)

    【讨论】:

    • 抱歉,OtherClass 中出现了一个巨大的错误。更改了我的问题以更好地反映您的初衷。
    猜你喜欢
    • 2012-12-31
    • 2014-08-27
    • 2014-04-13
    • 2012-04-05
    • 1970-01-01
    • 1970-01-01
    • 2013-06-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多