How to Test a Concern in Rails

Ruby on-RailsTestingRspec

Ruby on-Rails Problem Overview


Given that I have a Personable concern in my Rails 4 application which has a full_name method, how would I go about testing this using RSpec?

concerns/personable.rb

module Personable
  extend ActiveSupport::Concern

  def full_name
    "#{first_name} #{last_name}"
  end
end

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

The method you found will certainly work to test a little bit of functionality but seems pretty fragile—your dummy class (actually just a Struct in your solution) may or may not behave like a real class that includes your concern. Additionally if you're trying to test model concerns, you won't be able to do things like test the validity of objects or invoke ActiveRecord callbacks unless you set up the database accordingly (because your dummy class won't have a database table backing it). Moreover, you'll want to not only test the concern but also test the concern's behavior inside your model specs.

So why not kill two birds with one stone? By using RSpec's shared example groups, you can test your concerns against the actual classes that use them (e.g., models) and you'll be able to test them everywhere they're used. And you only have to write the tests once and then just include them in any model spec that uses your concern. In your case, this might look something like this:

# app/models/concerns/personable.rb
module Personable
  extend ActiveSupport::Concern

  def full_name
    "#{first_name} #{last_name}"
  end
end

# spec/concerns/personable_spec.rb
require 'spec_helper'

shared_examples_for "personable" do
  let(:model) { described_class } # the class that includes the concern

  it "has a full name" do
    person = FactoryBot.build(model.to_s.underscore.to_sym, first_name: "Stewart", last_name: "Home")
    expect(person.full_name).to eq("Stewart Home")
  end
end

# spec/models/master_spec.rb
require 'spec_helper'
require Rails.root.join "spec/concerns/personable_spec.rb"

describe Master do
  it_behaves_like "personable"
end

# spec/models/apprentice_spec.rb
require 'spec_helper'

describe Apprentice do
  it_behaves_like "personable"
end

The advantages of this approach become even more obvious when you start doing things in your concern like invoking AR callbacks, where anything less than an AR object just won't do.

Solution 2 - Ruby on-Rails

In response to the comments I've received, here's what I've ended up doing (if anyone has improvements please feel free to post them):

spec/concerns/personable_spec.rb

require 'spec_helper'

describe Personable do
  let(:test_class) { Struct.new(:first_name, :last_name) { include Personable } }
  let(:personable) { test_class.new("Stewart", "Home") }

  it "has a full_name" do
    expect(personable.full_name).to eq("#{personable.first_name} #{personable.last_name}")
  end
end

Solution 3 - Ruby on-Rails

Another thought is to use the with_model gem to test things like this. I was looking to test a concern myself and had seen the pg_search gem doing this. It seems a lot better than testing on individual models, since those might change, and it's nice to define the things you're going to need in your spec.

Solution 4 - Ruby on-Rails

The following worked for me. In my case my concern was calling generated *_path methods and the others approaches didn't seem to work. This approach will give you access to some of the methods only available in the context of a controller.

Concern:

module MyConcern
  extend ActiveSupport::Concern

  def foo
    ...
  end
end

Spec:

require 'rails_helper'

class MyConcernFakeController < ApplicationController
  include MyConcernFakeController
end

RSpec.describe MyConcernFakeController, type: :controller do    
  context 'foo' do
    it '' do
      expect(subject.foo).to eq(...)
    end
  end
end

Solution 5 - Ruby on-Rails

just include your concern in spec and test it if it returns the right value.

RSpec.describe Personable do
  include Personable

  context 'test' do
    let!(:person) { create(:person) }

    it 'should match' do
       expect(person.full_name).to eql 'David King'
    end
  end
end

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKyle DecotView Question on Stackoverflow
Solution 1 - Ruby on-RailsJosh LeitzelView Answer on Stackoverflow
Solution 2 - Ruby on-RailsKyle DecotView Answer on Stackoverflow
Solution 3 - Ruby on-RailslobatiView Answer on Stackoverflow
Solution 4 - Ruby on-RailsNathan WillsonView Answer on Stackoverflow
Solution 5 - Ruby on-RailsJin LimView Answer on Stackoverflow