web-dev-qa-db-ja.com

Rails&RSpec-Concernsクラスメソッドのテスト

私は以下を持っています(簡略化)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でテストする良い方法がわかりません。ダミークラスを作成しただけでは、検証が機能していることを確認しようとすると、アクティブレコードエラーが発生します。他の誰かがこの問題に直面しましたか?

22
Bryce

RSpec shared examples を確認してください。

これにより、次のように記述できます。

# 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
43
Aaron K

私はこれに苦労して、次の解決策を思いつきました。これは、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
7
Martijn

このモジュールを含むクラスにテストを残すことで、モジュールを暗黙的にテストできます。または、ダミークラスに他の必須モジュールを含めることもできます。たとえば、ARモデルのvalidatesメソッドはActiveModel::Validationsによって提供されます。だから、あなたのテストのために:

class DummyClass
  include ActiveModel::Validations
  include HasTerms
end

HasTermsモジュールで暗黙的に依存する依存関係に基づいて、他のモジュールを組み込む必要がある場合があります。

7
rossta

これは別の例です(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
4
Konrad Janczyk

Aaron Kの優れた答え here を基に、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
3
Neal