RSpec Custom Matchers

Everyone knows what are RSpec matchers when you are writing RSpec code for your project. If you don’t know, it’s better to read before read this article. Just google it ‘as rspec matchers’ you will be find many links and follow any one link which you like.

By default, RSpec provide us many matchers. At the same time RSpec provide us a way to create our custom matchers depends upon our requirements. Now let see how to write our own custom matchers.

In this article I have written matchers for presence, uniqueness and format validations.

Now place following code on ‘spec/support/custom/matchers.rb’

# spec/support/custom/matchers.rb
# Following matchers are useful if you are using mongodb with mongoid
module Custom
  module Matcher
   RSpec::Matchers.define :be_present do |attribute|
     match do |target|
       attributes = target.class.validators.collect{|a| a.attributes if a.class ==                                     ActiveModel::Validations::PresenceValidator}.compact.flatten
       attributes.include?(attribute)
     end

     failure_message_for_should do |target|
       "expecting true but got false"
     end

     failure_message_for_should_not do |target|
       "expecting false but got true"
     end
   end

   RSpec::Matchers.define :be_unique do |attribute|
      match do |target|
        attributes = target.class.validators.collect{|a| a.attributes if a.class ==  Mongoid::Validations::UniquenessValidator}.compact.flatten
        attributes.include?(attribute)
      end

      failure_message_for_should do |target|
        "expecting true but got false"
      end

      failure_message_for_should_not do |target|
         "expecting false but got true"
      end
    end

    RSpec::Matchers.define :be_in_valid_format do |attribute|
       match do |target|
          attributes = target.class.validators.collect{|a| a.attributes if a.class == ActiveModel::Validations::FormatValidator}.compact.flatten
          attributes.include?(attribute)
       end

       failure_message_for_should do |target|
         "expecting true but got false"
       end

       failure_message_for_should_not do |target|
          "expecting false but got true"
       end
     end
  end
end

Now you if you want test uniqueness of an attribute then just write in your spec file as

#spec/models/user.rb

   subject { User.new }
   it { should be_unique(:email) }

Explanation:

When your are executing user specs ‘be_unique’ will call our predefine matcher on object which we set in subject block. In our case the subject is user object. After that our matcher code is executed and then return either true or false as a result. Depends on return value and expectation we have used our test case will pass or fail.

Not only we can define our own matchers but we can override the failure messages by over-riding ‘failure_message_for_should’ and ‘failure_message_for_should_not’ methods. Whenever any failure encountered these methods will invoked automatically.

I hope you have learned something new today. Any feedback or suggestions would be welcome.