I won’t even attempt to discuss the utility of concerns. To some people, they’re a dangerous way of hiding dependency and just syntactic sugar for include/extend. To others, they should be used whenever possible. To me, concerns are just a tool. I try not to use them too much, but I don’t freak out when I see them either.
Instead, I’m going to focus on the practical side of the matter: what is the best approach to testing your concerns?
Let’s assume you have the following Processable concern:
require 'active_support/concern'
module Processable
extend ActiveSupport::Concern
included do
scope :processed, ->{ where processed: true }
scope :unprocessed, ->{ where processed: false }
end
module ClassMethods
# nothing here
end
def mark_as_processed!
update_column :processed, true
end
def mark_as_unprocessed!
update_column :processed, false
end
def status
if processed?
:processed
else
:unprocessed
end
end
def unprocessed?
!processed?
end
end
And you have a generic Task model that includes it:
class Task < ActiveRecord::Base
include Processable
end
The most obvious approach would be to test the concern’s methods directly on the model. But what if you include the concern in more than one model? (That’s what concerns are for, after all!) Well, you can go two ways from here:
Since there are plenty of articles out there on RSpec shared examples, I will focus solely on the second approach.
What we’re going to do is create a model that has only the columns used by the concern. The model will then include that concern, allowing us to test instance and class methods.
However, we don’t want to create an actual model just for testing purposes, do we? That’s where the Temping gem comes in handy.
Let’s get down to business, then. First of all, you’ll have to install the gem by adding it to your Gemfile (you can put it in the test group).
Once it’s done, you can create your test like this:
require 'rails_helper'
RSpec.describe Processable do
before(:all) do
Temping.create :processable_model do
include Processable
with_columns do |t|
t.boolean :processable, default: false
end
end
end
subject { ProcessableModel.new }
# ...
end
What we’re doing, here, is tell Temping to create a temporary model named ProcessableModel with the processable boolean column. This model includes the Processable concern.
After the model has been set up, you can do whatever you want: Create new instances, call class and instance methods and pretty much anything else you would do with an Active Record model.
For example, let’s test the mark_as_processed! method:
require 'rails_helper'
RSpec.describe Processable do
# ...
describe '#mark_as_processed!' do
it 'updates the processed column to true' do
subject
.expects(:update_column)
.with(:processed, true)
.once
.returns(true)
subject.mark_as_processed!
end
end
# ...
end
Cool, right? It’s faster than RSpec shared examples (because you test your concern exactly once, not once for each model that includes it) and extremely flexible.
Alright, that’s it. Testing the concern’s scopes and the other methods is up to you. Have fun!