Posted almost 7 years ago. Visible to the public. Repeats.

Testing ActiveRecord validations with RSpec

Validations should be covered by a model's spec.

This card shows how to test an individual validation. This is preferrable to save an entire record and see whether it is invalid.

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.validate
  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.validate
  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.validate expect(record.errors[:email]).to include("can't be blank") # check for presence of error record.email = 'foo@bar.com' # valid state record.validate expect(record.errors[:email]).to_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 Archive 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 { is_expected_to validate_presence_of(:screen_name) } end describe '#email' do it { is_expected_to_not allow_value("blah").for(:email) } it { is_expected_to allow_value("a@b.com").for(:email) } end describe '#age' do it { is_expected_to validate_inclusion_of(:age).in_range(1..100) } end end

See the shoulda-matchers README Archive 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 '#validate', I prefer to sort validation tests under the example group for the method that is being validated. This way they will be right next to defaults and other behavior for that method:

Copy
describe Report do describe '#currency' do it { is_expected_to allow_values('EUR', 'USD', 'GBP', 'CHF').for(:currency) } it { is_expected_to_not allow_values('foo', '€', '', nil).for(:currency) } it 'defaults to EUR' do expect(subject.currency).to eq('EUR') end end describe '#year' do it { is_expected_to validate_presence_of(:year) } it 'defaults to the current year' do expect(subject.year).to 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 Archive . 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 "validates that it's not a palindrome" do subject.screen_name = 'Hannah' subject.validate expect(subject.errors[:screen_name]).to include('must not be a palindrome') subject.screen_name = 'Johanna' subject.validate expect(subject.errors[:screen_name]).to_not include('must not be a palindrome') end end end

Does your version of Ruby on Rails still receive security updates?
Rails LTS provides security patches for unsupported versions of Ruby on Rails (2.3, 3.2, 4.2 and 5.2).

Owner of this card:

Avatar
Henning Koch
Last edit:
5 months ago
by Felix Eschey
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 short-lived cookies to improve usability.
Accept or learn more