Rails和RSpec – 测试关注类方法

我有以下(简化)Rails关注:

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中测试这个模块的好方法 – 如果我只创建一个虚拟类,当我尝试检查validation是否正常时,我会收到活动记录错误。 还有其他人遇到过这个问题吗?

查看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 

您可以通过将测试留在包含此模块的类中来隐式地测试模块。 或者,您可以在虚拟类中包含其他必需的模块。 例如,AR模型中的validates方法由ActiveModel::Validations 。 所以,对于你的测试:

 class DummyClass include ActiveModel::Validations include HasTerms end 

根据您在HasTerms模块中隐式依赖的依赖关系,可能需要引入其他模块。

我正在努力解决这个问题,并想出以下解决方案,这很像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 

这是另一个例子(使用Factorygirl的“创建”方法“和shared_examples_for)

关注规格

 #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 

而restraunt_spec可能看起来像这样(我不是Rspec大师,所以不要以为我编写规范的方式是好的 – 最重要的是在开头):

 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 

基于Aaron K 在这里的优秀答案,你可以使用一些很好的技巧,RSpec提供的described_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