Ruby on rails 铁路及;RSpec-测试关注类方法

Ruby on rails 铁路及;RSpec-测试关注类方法,ruby-on-rails,ruby-on-rails-3,rspec,module,Ruby On Rails,Ruby On Rails 3,Rspec,Module,我有以下(简化的)问题: module HasTerms extend ActiveSupport::Concern module ClassMethods def optional_agreement # Attributes #---------------------------------------------------------------------------- attr_accessible :agrees_to_ter

我有以下(简化的)问题:

module HasTerms
  extend ActiveSupport::Concern

  module ClassMethods
    def optional_agreement
      # Attributes
      #----------------------------------------------------------------------------
      attr_accessible :agrees_to_terms
    end

    def required_agreement
      # Attributes
      #----------------------------------------------------------------------------
      attr_accessible :agrees_to_terms

      # Validations
      #----------------------------------------------------------------------------
      validates :agrees_to_terms, :acceptance => true, :allow_nil => :false, :on => :create
    end
  end
end

但是,我想不出一个在RSpec中测试这个模块的好方法——如果我只是创建一个虚拟类,那么当我尝试检查验证是否正常工作时,就会出现活动记录错误。还有其他人遇到过这个问题吗?

您可以将测试留在包含该模块的类中,从而隐式地测试该模块。或者,您可以在虚拟类中包含其他必需的模块。例如,AR模型中的
validates
方法由
ActiveModel::Validations
提供。因此,对于您的测试:

class DummyClass
  include ActiveModel::Validations
  include HasTerms
end
根据
HasTerms
模块中隐含依赖的依赖关系,可能需要引入其他模块

查看RSpec

通过这种方式,您可以编写以下内容:

# spec/support/has_terms_tests.rb
shared_examples "has terms" do
   # Your tests here
end


# spec/wherever/has_terms_spec.rb
module TestTemps
  class HasTermsDouble
    include ActiveModel::Validations
    include HasTerms
  end
end

describe HasTerms do

  context "when included in a class" do
    subject(:with_terms) { TestTemps::HasTermsDouble.new }

    it_behaves_like "has terms"
  end

end


# spec/model/contract_spec.rb
describe Contract do

  it_behaves_like "has terms"

end

我自己也在努力解决这个问题,并想出了以下解决方案,这与rossta的想法非常相似,但使用了匿名类:

it 'validates terms' do
  dummy_class = Class.new do
    include ActiveModel::Validations
    include HasTerms

    attr_accessor :agrees_to_terms

    def self.model_name
      ActiveModel::Name.new(self, nil, "dummy")
    end
  end

  dummy = dummy_class.new
  dummy.should_not be_valid
end

在Aaron K的优秀答案的基础上,RSpec提供了一些很好的技巧,可用于
descripted\u class
,使您的方法无处不在,并使工厂为您服务。下面是我最近为一个应用程序制作的共享示例的一个片段:

shared_examples 'token authenticatable' do
  describe '.find_by_authentication_token' do
    context 'valid token' do
      it 'finds correct user' do
        class_symbol = described_class.name.underscore
        item = create(class_symbol, :authentication_token)
        create(class_symbol, :authentication_token)

        item_found = described_class.find_by_authentication_token(
          item.authentication_token
        )

        expect(item_found).to eq item
      end
    end

    context 'nil token' do
      it 'returns nil' do
        class_symbol = described_class.name.underscore
        create(class_symbol)

        item_found = described_class.find_by_authentication_token(nil)

        expect(item_found).to be_nil
      end
    end
  end
end
下面是另一个示例(使用Factorygirl的“创建”方法和共享的示例)

关注点规范 可评论的关注 并且restranu规范可能看起来像这样(我不是Rspec大师,所以不要认为我编写规范的方式是好的-最重要的是在开始时):


我同意隐式测试很容易,但我觉得我也需要能够显式测试。这在编写我的类还没有使用过的类函数时尤其重要。像
DummyClass
这样的东西是你当时正在寻找的替代品。这显然是最好的答案。可以是exp在虚拟情况下合法,并在父类的规范中测试相同的API。这在处理任何“灵活”API(读取:method_missing)时会产生很大的不同。只有在“真实”(非虚拟)中使用它时,才会想到一些情况类,共享示例将很好地在每个必要的上下文中练习代码。当您的模块添加动态属性时,这会出现问题。假设您的模块允许类方法:
允许上传:csv
,它添加了
csv\u文件路径
csv\u文件大小
等方法。但您有另一种模式l调用上载文件
:附件
。现在您的“文件”充当上载文件“规范将失败,因为一个正在添加
csv\u文件路径
,另一个有
附件文件路径
。出于这个原因,我觉得在很多情况下,使用一个虚拟类来测试模块的行为将最适合您的需要,如@Martijn的answer@nzifnab明确地说,模块没有添加方法,子类是显式的。在这里,共享示例是否合适是特定于代码库的判断调用。但是,您仍然可以以这种方式使用它们。可以向他们传递信息,就像您在通话中所做的那样:
它的行为类似于“作为上传”,csv
@AaronK Oh!我不知道你可以传递一个值给它。事实证明,您还可以给它一个块,为共享示例提供一些上下文。类似于:
它的行为类似于“上传”;:csv do;主题{Upload.new(文件:some_dummy_文件)};结束
谢谢你的提示:p这是我找到的唯一有效的解决方案。当使用命名的虚拟类时,如果调用修改该类的方法,那么下一个规范也将看到该类的修改,那么您的规范将开始相互渗透。尽管我的更像是
let(:dummy_class){class.new(…)}
,而不是直接插入
it
块。
#spec/support/concerns/commentable_spec
require 'spec_helper'
shared_examples_for 'commentable' do
  let (:model) { create ( described_class.to_s.underscore ) }
  let (:user) { create (:user) }

  it 'has comments' do
    expect { model.comments }.to_not raise_error
  end
  it 'comment method returns Comment object as association' do
    model.comment(user, "description")
    expect(model.comments.length).to eq(1)
  end
  it 'user can make multiple comments' do
    model.comment(user, "description")
    model.comment(user, "description")
    expect(model.comments.length).to eq(2)
  end
end
module Commentable
  extend ActiveSupport::Concern
  included do
    has_many :comments, as: :commentable
  end

  def comment(user, description)
    Comment.create(commentable_id: self.id,
                  commentable_type: self.class.name,
                  user_id: user.id,
                  description: description
                  )
  end

end
require 'rails_helper'

RSpec.describe Restraunt, type: :model do
  it_behaves_like 'commentable'

  describe 'with valid data' do
    let (:restraunt) { create(:restraunt) }
    it 'has valid factory' do
      expect(restraunt).to be_valid
    end
    it 'has many comments' do
      expect { restraunt.comments }.to_not raise_error
    end
  end
  describe 'with invalid data' do
    it 'is invalid without a name' do
      restraunt = build(:restraunt, name: nil)
      restraunt.save
      expect(restraunt.errors[:name].length).to eq(1)
    end
    it 'is invalid without description' do
      restraunt = build(:restraunt, description: nil)
      restraunt.save
      expect(restraunt.errors[:description].length).to eq(1)
    end
    it 'is invalid without location' do
      restraunt = build(:restraunt, location: nil)
      restraunt.save
      expect(restraunt.errors[:location].length).to eq(1)
    end
    it 'does not allow duplicated name' do
      restraunt = create(:restraunt, name: 'test_name')
      restraunt2 = build(:restraunt, name: 'test_name')
      restraunt2.save
      expect(restraunt2.errors[:name].length).to eq(1)
    end
  end
end