Introduction
Shoulda Matchers provides Test::Unit and RSpec-compatible one-liners that test common Rails functionality. It helps you write tests that would otherwise be much longer, more complex, and error-prone.
Using shoulda-matchers for testing simplifies the entire process.
Let’s take a look at shoulda-matchers in action, compared to its RSpec-only equivalent.
One-Liner ActiveModel Tests
Below is an RSpec unit test for an ActiveModel. Each named test goes through four distinct phases: setup, exercise, verify, and teardown. The first named test ensures that you can’t create a valid Contact record without a first_name
attribute; the second test makes sure each Contact record has a unique email
address, and the third enforces a minimum password
length requirement.
describe Contact do
it "is invalid without a firstname" do
contact = Contact.create(first_name: nil, email: 'ted@example.com', password: 'empirestatebuilding')
contact.valid?
expect(contact.errors[:firstname]).to include("can't be blank")
end
it "is invalid with a duplicate email address" do
Contact.create(first_name: 'Ted', email: 'ted@example.com', password: 'empirestatebuilding')
contact = Contact.new(first_name: 'Robin', email: 'ted@example.com', password: 'montrealcanadiens')
contact.valid?
expect(contact.errors[:emal]).to include("has already been taken")
end
it "is invalid without a minimum password length" do
contact_one = Contact.create(first_name: 'Ted', email: 'ted@example.com', password: 'empire')
contact_one.valid?
expect(Contact.errors[:password]).to include("password is too short")
end
end
At every phase of the RSpec-only test cases, you need to recall or reference too much information to correctly implement each one. You need to remember that Rails stores error messages as a Hash, and you also need to remember the exact text for each error message.
That alone can discourage you from testing your Rails app because it slows you down.
In this example, shoulda-matchers reduces about 20 lines of test code to just three, but gives you the same assurance that you can change your code at any time.
describe Contact do
it { is_expected.to validate_presence_of(:firstname) }
it { is_expected.to validate_uniqueness_of(:email) }
it { is_expected.to validate_length_of(:password).is_at_least(10) }
end
One-Liner ActionController Tests
In Rails, it’s essential for each controller action to have tests that assert the core HTTP response properties.
For a basic Rails controller, you’d test the following:
- What was the response status code?
- Did the controller render the expected template?
- Did the controller render the expected Rails layout?
- Did the controller set any flash messages?
- Was any information inserted or deleted from the session?
- Did the controller redirect the user to a new URL?
Here’s what such a controller test might look like using shoulda-matchers:
describe ContactsController do
describe 'GET #index' do
context 'when user is logged in' do
with :user
before do
sign_in user
get :index
end
it { is_expected.to respond_with :ok }
it { is_expected.to render_with_layout :application }
it { is_expected.to render_template :index }
end
context 'when user is logged out' do
before do
get :index
end
it { is_expected.to redirect_to new_session_path }
it { is_expected.to set_the_flash(:warning).to('Please log in.') }
it { is_expected.to set_session(:return_to).to(contacts_path) }
end
end
end
Controllers are rarely tested in Rails apps because in behavior-driven development a feature spec usually covers the work of multiple controller specs.
Still, shoulda-matchers makes it a lot easier to test a controller method.
One-Liner ActiveRecord Tests
Since ActiveRecord associations are well-tested by the Rails test suite, most people just trust that they’ll work, and don’t feel the need to test them.
However, if you want to make sure that your models are using any associations you’ve declared, shoulda-matchers can help you accomplish that easily.
Here’s how you’d write a has_many
association for a Contact record, assuming it has many phone numbers using just RSpec:
describe Contact do
it "can have many phone numbers" do
contact = Contact.reflect_on_association(:phones)
expect(contact.macro).to eql(:has_many)
end
end
The shoulda-matchers version of the has_many
association is easier to read, and clearly states the intent of your test. Adding a qualifier is just a matter of chaining the methods available to you.
describe Contact do
it { is_expected.to have_many(:phones).dependent(:destroy) }
end
As shown in the examples above, shoulda-matchers reduces the amount of test code you need to write for your Rails application — you can write robust tests that aren’t lengthy or complicated. The official documentation for shoulda-matchers shows you more one-liners, along with information about how to use them.
P.S. Would you like to learn how to build sustainable Rails apps and ship more often? We’ve recently published an ebook covering just that — “Rails Testing Handbook”. Learn more and download a free copy.