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.
In general any validation test for an attribute :attribute_being_tested
looks like this:
record
below)record.validate
record.errors[:attribute_being_tested]
contains the expected validation errorrecord.validate
record.errors[:attribute_being_tested]
does not contain the expected validation errorFor instance, we want to test that User#email
is present:
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
The
shoulda-matchers
Show archive.org snapshot
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:
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
Show archive.org snapshot
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:
describe User do
describe '#screen_name' do
it { is_expected.to validate_presence_of(:screen_name) }
end
end
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:
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
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
Show archive.org snapshot
. Since that check is not possible with standard Rails validations, we write a custom validation method like this:
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:
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
Checking the human errors messages
subject.validate
expect(subject.errors(:bar)).to contain_exactly('some error')
subject.validate
expect(subject.errors(:bar)).to eq(['some error'])
Checking the error message key
subject.validate
expect(subject.errors).to be_of_kind(:bar, :invalid)