Using Shoulda Matchers with RSpec
A guide to setting up Shoulda Matchers to work with RSpec
Shoulda Matchers is a great tool to use to create one line tests for common functionality in Rails apps. I use them to test associations and validations within my Rails apps.
So if we have a model like this:
models/article.rb
class Article < ApplicationRecord
validates :title, presence: true
end
Instead of doing this:
spec/models/article_spec.rb
RSpec.describe Article, type: :model do
subject {
described_class.new(title: 'Article Title')
}
it 'is valid with valid attributes' do
expect(subject).to be_valid
end
it 'is not valid without a title' do
subject.title = nil
expect(subject).to_not be_valid
end
end
We can use Shoulda Matchers to write this:
spec/models/article_spec.rb
RSpec.describe Article, type: :model do
describe 'Validations' do
it { should validate_presence_of(:title) }
end
end
These one liners are less error prone and easier to write.
So onto the setup! First of all we will want to add the Shoulda Matchers gem to our Gemfile in the test
block:
Gemfile
group :test do
gem 'should-matchers'
end
Then we will want to run bundle install to install the dependency within our project.
I use Shoulda Matchers within my Rails projects and this is how I set it up:
spec/rails_helper.rb
Shoulda::Matchers.configure do |config|
config.integrate do |with|
with.test_framework :rspec
with.library :rails
end
end
Shoulda Matchers can be set up to work with other test frameworks and non Rails apps.
Shoulda Matchers should now be set up to work within your project.
As I mentioned above I use Shoulda Matchers to test my Rails associations and validations. So we can have ‘simple’ tests like:
RSpec.describe Article, type: :model do
describe 'Associations' do
it { should belong_to(:user) }
it { should have_many(:comments) }
end
describe 'Validations' do
it { should validate_presence_of(:title) }
end
end
Occasionally you will have to build a valid object before using a matcher (Shoulda Matchers by default creates fresh models), for instance when validating uniqueness on some types of object:
describe 'Validations' do
describe 'name uniqueness' do
subject { build(:article) } # We use FactoryBot to build instances of our models
it { should validate_uniqueness_of(:title) }
end
end
My most commonly used Shoulda Matchers are:
describe 'Associations' do
it { should belong_to(:user) }
it { should have_and_belong_to_many(:collections) }
it { should have_many(:comments)
end
describe 'Validations' do
it { should validate_presence_of(:title) }
it { should validate_uniqueness_of(:title) }
it { should validate_numericality_of(:phone_number) }
it { should allow_value('', nil, 1).for(:phone_number) } # if we allow blank in model
it { should_not allow_value(-1, 31, 'no').for(:position) } # if we only allow positive integers between 0 and 30
end
You can find a complete list of matchers within the Shoulda Matchers GitHub repo.
Shoulda Matchers also allow for more complex chaining. So if we had:
class Article < ApplicationRecord
has_one :cover_image, class_name: 'Attachment::Image', inverse_of: :article, dependent: :destroy
end
We could use Shoulda Matchers to write:
RSpec.describe Article, type: :model do
describe 'Associations' do
it { should have_one(:cover_image).class_name('Attachment::Image').dependent(:destroy) }
end
end