Posted about 3 years ago. Visible to the public. Repeats.

Testing ActiveRecord validations with RSpec

Validations should be covered by a model's spec.

Recipe for testing any validation

In general any validation test for an attribute :attribute_being_tested looks like this:

  1. Make a model instance (named record below)
  2. Run validations by saying record.valid?
  3. Check if record.errors[:attribute_being_tested] contains the expected validation error
  4. Put the attribute into a valid state
  5. Run validations again by saying record.valid?
  6. Check if record.errors[:attribute_being_tested] does not contain the expected validation error

For instance, we want to test that User#email is present:

Copy
describe User do describe '#email' do it 'validates presence' do record = User.new record.email = '' # invalid state record.valid? # run validations record.errors[:email].should include("can't be blank") # check for presence of error record.email = 'foo@bar.com' # valid state record.valid? # run validations record.errors[:email].should_not include("can't be blank") # check for absence of error end end end

Standard validations can be tested with less code

The shoulda-matchers gem gives you some RSpec matchers to test the application of standard Rails validations. Under the hood should-matchers uses the same recipe as outlined above (set invalid state, run validations, check for message, etc.), but your tests will have a lot less code:

Copy
describe User do describe '#screen_name' do it { should validate_presence_of(:screen_name) } end describe '#email' do it { should_not allow_value("blah").for(:email) } it { should allow_value("a@b.com").for(:email) } end describe '#age' do it { should validate_inclusion_of(:age).in_range(1..100) } end end

See the shoulda-matchers README for a full list of matchers provided. See our dedicated card for should validate_uniqueness_of, it works differently than you think.

Note that in RSpec 3's expect syntax, you use is_expected.to for the shorthand it { should ... } syntax:

Copy
describe User do describe '#screen_name' do it { is_expected.to validate_presence_of(:screen_name) } end end

Where to put validation examples

We usually sort our specs by method name.

While a case can be made by putting validation tests under describe '#valid?', I prefer to sort validation tests under the example group for the method that is being validated. This way thay will be right next to defaults and other behavior for that method:

Copy
describe Report do describe '#currency' do it { should allow_values('EUR', 'USD', 'GBP', 'CHF').for(:currency) } it { should_not allow_values('foo', '€', '', nil).for(:currency) } it 'defaults to EUR' do subject.currency.should eq('EUR') end end describe '#year' do it { should validate_presence_of(:year) } it 'defaults to the current year' do subject.year.should eq(Date.today.year) end end end

Testing custom validation methods

The recipe above applies to custom validation methods as well.

Let's say you want to test that User#screen_name is not a palindrome. Since that check is not possible with standard Rails validations, we write a custom validation method like this:

Copy
class User < ActiveRecord::Base validate :validate_screen_name_is_no_palindrome private def validate_screen_name_is_no_palindrome if screen_name.downcase == screen_name.downcase.reverse errors.add(:screen_name, 'must not be a palindrome') end end end

We can now reply the test recipe from above:

Copy
describe User do describe '#screen_name' do it "should validate that it's not a palindrome" do subject.screen_name = 'Hannah' subject.valid? # run validations subject.errors[:screen_name].should include('must not be a palindrome') subject.screen_name = 'Johanna' subject.valid? # run validations subject.errors[:screen_name].should_not include('must not be a palindrome') end end end
Growing Rails Applications in Practice
Check out our new e-book:
Learn to structure large Ruby on Rails codebases with the tools you already know and love.

Owner of this card:

Avatar
Henning Koch
Last edit:
9 months ago
by Thomas Tasler
About this deck:
We are makandra and do test-driven, agile Ruby on Rails software development.
License for source code
Posted by Henning Koch to makandra dev
This website uses cookies to improve usability and analyze traffic.
Accept or learn more