ActiveSupport::Concern 的动态测试生成

Dynamic generation of tests for an ActiveSupport::Concern

我有一个定义如下的问题:

module Shared::Injectable 
  extend ActiveSupport::Concern

  module ClassMethods
    def injectable_attributes(attributes)
      attributes.each do |atr|
        define_method "injected_#{atr}" do
           ...
        end
      end
    end
  end

以及各种使用这种关注的模型:

Class MyThing < ActiveRecord::Base
  include Shared::Injectable
  ...
  injectable_attributes [:attr1, :attr2, :attr3, ...]
  ...
end

这按预期工作,并生成了一组我可以在 class:

的实例上调用的新方法
my_thing_instance.injected_attr1
my_thing_instance.injected_attr2
my_thing_instance.injected_attr3

当我试图测试问题时,我的问题就来了。我想避免为每个使用关注点的模型手动创建测试,因为生成的函数都做同样的事情。相反,我认为我可以使用 rspec 的 shared_example_for 并编写一次测试,然后只 运行 使用 rspec 的 [=16] 在必要的模型中进行测试=].这很好用,但我在访问传递给 injectable_attributes 函数的参数时遇到问题。

目前,我在共享规范中这样做:

shared_examples_for "injectable" do |item|
  ...
  describe "some tests" do
    attrs = item.methods.select{|m| m.to_s.include?("injected") and m.to_s.include?("published")}
    attrs.each do |a|
      it "should do something with #{a}" do
        ...
      end
    end
  end
end

这可行,但显然是一种糟糕的方法。有没有一种简单的方法可以仅通过 class 的实例或通过 class 本身访问传递给 injectable_attributes 函数的值,而不是查看已经定义的方法在 class 实例上?

尝试这样的事情怎么样:

class MyModel < ActiveRecord::Base
  MODEL_ATTRIBUTES = [:attr1, :attr2, :attr3, ...]
end

it_behaves_like "injectable" do 
  let(:model_attributes) { MyModel::MODEL_ATTRIBUTES }
end

shared_examples "injectable" do
  it "should validate all model attributes" do 
    model_attributes.each do |attr|
      expect(subject.send("injected_#{attr}".to_sym)).to eq (SOMETHING IT SHOULD EQUAL)
    end
  end
end

它不会为每个属性创建单独的测试用例,但它们应该对每个属性都有一个断言。这至少可以给你一些工作的机会。

既然你说你 "want to avoid manually creating the tests for every model that uses the concern, since the generated functions all do the same thing",那么单独测试模块的规范怎么样?

module Shared
  module Injectable
    extend ActiveSupport::Concern

    module ClassMethods
      def injectable_attributes(attributes)
        attributes.each do |atr|
          define_method "injected_#{atr}" do
            # method content
          end
        end
      end
    end
  end
end

RSpec.describe Shared::Injectable do
  let(:injectable) do
    Class.new do
      include Shared::Injectable

      injectable_attributes [:foo, :bar]
    end.new
  end

  it 'creates an injected_* method for each injectable attribute' do
    expect(injectable).to respond_to(:injected_foo)
    expect(injectable).to respond_to(:injected_bar)
  end
end

然后,作为一个选项,如果您想编写一个通用规范来测试对象是否确实具有可注入属性而不重复模块规范中的内容,您可以添加类似以下内容的内容您的 MyThing 规范文件:

RSpec.describe MyThing do
  let(:my_thing) { MyThing.new }

  it 'has injectable attributes' do
    expect(my_thing).to be_kind_of(Shared::Injectable)
  end
end