Testing a Concern/Module That Uses Activerecord

How to Test a Concern in 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.

Rails & RSpec - Testing Concerns class methods

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

Unit testing a module that is included in ActiveRecord models

First off, self.included is not a good way to describe your modules, and class_exec is needlessly complicating things. Instead, you should extend ActiveSupport::Concern, as in:

module Phoneable
extend ActiveSupport::Concern

included do
has_one :phone_number
validates_uniqueness_of :phone_number
end
end

You didn't mention what test framework you're using, but RSpec covers exactly this case. Try this:

shared_examples_for "a Phoneable" do
it "should have a phone number" do
subject.should respond_to :phone_number
end
end

Assuming your models look like:

class Person              class Business
include Phoneable include Phoneable
end end

Then, in your tests, you can do:

describe Person do
it_behaves_like "a Phoneable" # reuse Phoneable tests

it "should have a full name" do
subject.full_name.should == "Bob Smith"
end
end

describe Business do
it_behaves_like "a Phoneable" # reuse Phoneable tests

it "should have a ten-digit tax ID" do
subject.tax_id.should == "123-456-7890"
end
end

How to mock a method inside concern?

You can use allow to mock that method.

RSpec.describe BarService do
describe 'run' do
subject { described_class.new }

it do
allow(subject).to receive(:notify_slack).and_return(true)
# some tests on things other than notifying slack
subject.run
end
end
end


Related Topics



Leave a reply



Submit