we develop communication with weLaika Advertising

Validators Unit test in Ruby on Rails

Tags: ruby on rails, rspec, tdd, unit test
Filippo Gangi Dino -
Filippogangidino

When we want to test (with RSpec) a custom validator inside a Ruby on Rails model often the main temptation is to (integration) test only those models where the validator (or concern) is included.

For example we have a User model including a VatCodeFormat validator like this:

1
2
3
class User < ActiveRecord::Base
  validates :vat_code, vat_code_format: true
end

and a Validator like this:

1
2
3
4
5
6
7
8
9
class VatCodeFormatValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    if value =~ /\A[0-9]{11}\Z/i
      return true
    else
      record.errors[attribute] << I18n.t('errors.vat_code_invalid_format')
    end
  end
end

With integration tests we will write something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
require 'rails_helper'

RSpec.describe User, type: :model do
  context "custom validation" do
    context "vat code format validator" do
      it "returns 'vat code format invalid'" do
        user = build(:user, vat_code: "ABC")
        expect(user).to_not be_valid
        expect(user.errors.messages).to_not include("vat code format invalid")
      end

      it "returns 'vat code format valid'" do
        user = build(:user, vat_code: "1234567890A")
        expect(user).to be_valid
      end
    end
  end
end

The result is ok, but this strategy only tests integration between Model and Validators. If we want to unit test the VatCodeFormatValidator we should take this approach:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
require 'rails_helper'

RSpec.describe VatCodeFormatValidator do
  # Create the dummy class once when the test is run.
  before do
    stub_const("Validatable", Class.new).class_eval do
      include ActiveModel::Validations
      attr_accessor :vat_code
      validates :vat_code, vat_code_format: true
    end
  end

  subject { Validatable.new }

  context "with a valid VAT CODE" do
    it "validates valid vat code" do
      subject.vat_code = '12345678901'
      expect(subject).to be_valid
    end
  end

  context "with an invalid VAT CODE" do
    context "does not validate valid vat code" do
      it " with a wrong length" do
        subject.vat_code = '1234567890'
        expect(subject).to_not be_valid
      end

      it " with wrong chars" do
        subject.vat_code = '1234567890A'
        expect(subject).to_not be_valid
      end
    end
  end
end

In this example we create a Dummy Class inside rspec test including ActiveModel::Validations, with an attr_accessor equal to the field we want to validate and, finally, including the custom validator.

This is the easy way to unit test Validators.

Note: This approach can be also used for Concerns.