Rails & RSpec - Testing Concerns class methods

前端 未结 5 878
面向向阳花
面向向阳花 2020-12-14 17:09

I have the following (simplified) Rails Concern:

module HasTerms
  extend ActiveSupport::Concern

  module ClassMethods
    def optional_agreement
      # At         


        
相关标签:
5条回答
  • 2020-12-14 17:37

    Here is another example (using Factorygirl's "create" method" and shared_examples_for)

    concern spec

    #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
    

    commentable concern

    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
    

    and restraunt_spec may look something like this (I'm not Rspec guru so don't think that my way of writing specs is good - the most important thing is at the beginning):

    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
    
    0 讨论(0)
  • 2020-12-14 17:48

    Building on Aaron K's excellent answer here, there are some nice tricks you can use with described_class that RSpec provides to make your methods ubiquitous and make factories work for you. Here's a snippet of a shared example I recently made for an application:

    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
    
    0 讨论(0)
  • 2020-12-14 17:54

    You could just test the module implicitly by leaving your tests in the classes that include this module. Alternatively, you can include other requisite modules in your dummy class. For instance, the validates methods in AR models are provided by ActiveModel::Validations. So, for your tests:

    class DummyClass
      include ActiveModel::Validations
      include HasTerms
    end
    

    There may be other modules you need to bring in based on dependencies you implicitly rely on in your HasTerms module.

    0 讨论(0)
  • 2020-12-14 17:55

    Check out RSpec shared examples.

    This way you can write the following:

    # 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
    
    0 讨论(0)
  • 2020-12-14 17:59

    I was struggling with this myself and conjured up the following solution, which is much like rossta's idea but uses an anonymous class instead:

    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
    
    0 讨论(0)
提交回复
热议问题