使用rspec测试类方法是调用范围

时间:2015-06-02 21:47:12

标签: ruby-on-rails-4 rspec3

我为我的范围创建了rspec测试(scope1scope2scope3)并且它们按预期传递但我还想为类方法添加一些测试具有从我的控制器实际调用的内容(控制器通过此类方法间接调用范围):

def self.my_class_method(arg1, arg2)
  scoped = self.all

  if arg1.present?
    scoped = scoped.scope1(arg1)
  end

  if arg2.present?
    scoped = scoped.scope2(arg2)
  elsif arg1.present?
    scoped = scoped.scope3(arg1)
  end

  scoped
end

当我知道它们已经通过时,为这个类方法中的每个场景运行相同的范围测试似乎有点多余,所以我假设我真的只需要确保根据传入的args调用/应用不同的范围这个类方法。

有人可以就这个rspec测试的样子提出建议。

我认为这可能与

有关
expect_any_instance_of(MyModel.my_class_method(arg1, nil)).to receive(:scope1).with(arg1, nil)

但这不起作用。

我还要感谢确认,在我已经测试过范围的情况下,这是测试这种情况所必需的全部内容。

1 个答案:

答案 0 :(得分:0)

您编写的Rspec代码实际上是在测试方法的内部实现。您应该测试该方法返回您希望它返回给定参数的内容,而不是它以某种方式执行它。这样,您的测试就会变得不那么脆弱。例如,如果您更改了scope1被调用的内容,则您不必重写my_class_method次测试。

我会通过创建类的多个实例然后使用各种参数调用该方法并检查结果是否符合预期来实现。

我不知道scope1scope2做了什么,所以我做了一个示例,其中参数是模型的name属性,范围方法只是检索所有模型除了具有该名称的模型。显然,无论你的真实论点和scope方法是什么,你都应该把它放在你的测试中,你应该相应地修改预期的结果。

我将to_ary方法用于预期结果,因为self.all调用实际上返回了ActiveRecord关联,因此不会与预期的数组匹配。您可以使用includesdoes_not_includes代替eq,但也许您关心订单或其他内容。

describe MyModel do
  describe ".my_class_method" do
    # Could be helpful to use FactoryGirl here
    # Also note the bang (!) version of let
    let!(:my_model_1) { MyModel.create(name: "alex") }
    let!(:my_model_2) { MyModel.create(name: "bob") }
    let!(:my_model_3) { MyModel.create(name: "chris") }

    context "with nil arguments" do
      let(:arg1) { nil }
      let(:arg2) { nil }
      it "returns all" do
        expected = [my_model_1, my_model_2, my_model_3]
        expect_my_class_method_to_return expected
      end
    end

    context "with a first argument equal to a model's name" do
      let(:arg1) { my_model_1.name }
      let(:arg2) { nil }
      it "returns all except models with name matching the argument" do
        expected = [my_model_2, my_model_3]
        expect_my_class_method_to_return expected
      end

      context "with a second argument equal to another model's name" do
        let(:arg1) { my_model_1.name }
        let(:arg2) { my_model_2.name }
        it "returns all except models with name matching either argument" do
          expected = [my_model_3]
          expect_my_class_method_to_return expected
        end
      end
    end
  end

  private 

  def expect_my_class_method_to_return(expected)
    actual = described_class.my_class_method(arg1, arg2).to_ary
    expect(actual).to eq expected
  end
end