Reputation: 807
I have a model like this:
class User < ActiveRecord::Base
validates_length_of :name, :in => (2..5)
end
I want to test this validation:
it "should not allow too short name" do
u = User.new(:name => "a")
u.valid?
u.should have(1).error_on(:name)
end
But then it does not test which kind of error was set on name
. I want to know, if it was too_short
, too_long
, or maybe some other validation failed.
I can lookup the message text in errors array, like this:
u.errors[:name].should include(I18n.t("activerecord.errors.models.user.attributes.name.too_short"))
But this will fail when I set activerecord.errors.messages.too_short
in locale file instead of model-specific message.
So, is it possible to check which kind of error occured?
Upvotes: 29
Views: 21704
Reputation: 33537
Rails added an method to query for errors to ActiveModel in late 2011 and Rails v3.2. Just check to see if the appropriate error has been #added?
:
# An error added manually
record.errors.add :name, :blank
record.errors.added? :name, :blank # => true
# An error added after validation
record.email = '[email protected]'
record.valid? # => false
record.errors.added? :email, :taken, value: '[email protected]' # => true
Note that for validations that are parametrized (e.g. :greater_than_or_equal_to
) you'll need to pass the value of the parameter too.
record.errors.add(:age, :greater_than_or_equal_to, count: 1)
record.errors.added?(:age, :greater_than_or_equal_to, count: 1)
Errors are identified by their i18n key. You can find the appropriate keys to check in the appropriate Rails i18n file for any language under the error section.
Some other nifty questions you can ask ActiveModel#Error
are #empty?
and
#include?(attr)
, as well as anything you can ask an Enumerable
.
Upvotes: 43
Reputation: 721
The approach I use:
it "should not allow too short name" do
u = User.new(:name => "a")
expect{u.save!}.to raise_exception(/Name is too short/)
end
I use a regex to match the exception message because there may be many validation messages in the exception message, but we want to ensure that it contains a specific snippet relating to the name being too short.
This approach does couple your assertions to your validation messages, so if you every modify your validation message you'll likely need to modify your specs as well. Overall though, this is a simple way to assert validations are doing their job.
Upvotes: 5
Reputation: 807
I really don't like the idea of looking for translated error messages in Errors hash. After a conversation with a fellow Rubyists, I ended monkey patching Errors hash, so it saves the non-translated message first.
module ActiveModel
class Errors
def error_names
@_error_names ||= { }
end
def add_with_save_names(attribute, message = nil, options = {})
message ||= :invalid
if message.is_a?(Proc)
message = message.call
end
error_names[attribute] ||= []
error_names[attribute] << message
add_without_save_names(attribute, message, options)
end
alias_method_chain :add, :save_names
end
end
Then you can test like this:
u = User.new(:name => "a")
u.valid?
u.errors.error_names[:name].should include(:too_short)
Upvotes: 12
Reputation: 51697
I recommend checking out the gem shoulda for handling these types of repetitive validation tests. It complements RSpec or Test::Unit so you can write concise specs such as:
describe User do
it { should ensure_length_of(:name).is_at_least(2).is_at_most(5) }
end
Upvotes: 5