Friday, May 6, 2016

How to test model validations with validation type

While reading a code or while doing code review, I have see many times people write a test cases for model validation like this,
Before
# app/models/post.rb
class Post < ActiveRecord::Base
  validates :title, presence: true
end
# test/models/post_test.rb
test "should have the necessary required validators" do
  post = Post.new

  assert_not post.valid?
  assert post.errors.has_key(:title)
  # or some times
  assert_equal ["can't be blank"], post.errors.messages[:title]
end
And if same attribute has more than one validation then,
# app/models/post.rb
class Post < ActiveRecord::Base
  validates :title, presence: true, length: { in: 10..60 }
end
Most of the people do,
test "should have the necessary required validators" do
  post = Post.new

  assert_not post.valid?
  assert_equal ["can't be blank", "is too short (minimum is 10 characters)"], post.errors.messages[:title]
end
I think instead we can use symbol for it. Rails internally uses I18n locale support for error messages. For more details have a quick look on reference links provided below.

Means we can take a benifits from it. So improved test case code is,
After
test "should have the necessary required validators" do
  post = Post.new
  assert_not post.valid?
  
  assert post.errors.added? :title, :blank
  assert post.errors.added? :title, :too_short, { count: 20 }
end
While for custom validate method, you can use same approch by defining new key. Key can be any valid symbol.
Before
class Post < ActiveRecord::Base
  has_many :tags

  validates :title, presence: true
  validate :validate_tags_count
  
  private
    def validate_tags_count
      errors.add(:tags, "required alteast 2 tags") if tags.reject(&:marked_for_destruction?).count < 2
    end
end
After
class Post < ActiveRecord::Base
  has_many :tags

  validates :title, presence: true
  validate :validate_tags_count
  
  private
    def validate_tags_count
      errors.add(:tags, :required, count: 2) if tags.reject(&:marked_for_destruction?).count < 2
    end
end
test "should have the necessary required validators" do
  post = Post.new

  assert_not post.valid?
  assert post.errors.added? :tags, :required, { count: 2 }
end
#config/locales/en.yml
# Globally for all models
en:
  errors:
    messages: 
      required: "minimum %{count} tags required" 

or
 
#config/locales/en.yml
# only to post models
en:
  activerecord:
    errors:
      models:
        post:
          attributes:
            tags:
              required: "minimum %{count} tags required"
Same concept will work on rails 5 as well. Tested with rails 5.0.0.beta4.

References :
http://api.rubyonrails.org/classes/ActiveModel/Errors.html
activemodel/lib/active_model/validations/presence.rb
activemodel/lib/active_model/locale/en.yml

No comments:

Post a Comment