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.