Testing instance methods with RSpec Rails

Kavita Jadhav
4 min readMay 13, 2019
https://www.360logica.com/blog/use-unit-testing

Rspec is a commonly used testing framework for writing unit tests of Rails/Ruby applications. A unit test is written to test the behaviour of a single method. When it has a conditional branching then, it's good to write a separate test for each branching.

To test a method or a branch of code following attributes are required:
1. Data setup
2. Mocks/Exceptions
3. Method call
4. Assertion

Let's have a look at each attribute:

  1. Data setup — Each test needs some data to be used as method parameters or make decisions or call an actual method that needs to be tested. Each test should create its required data and after execution of the test, it should also delete it. In case you need to create a file for the execution of the test make sure you delete the file after the execution of the test.
  2. Mocks/Exceptions — In the unit test user intended to test the behaviour of the given method. If the method is calling another method inside it then that method call should be stubbed, as a separate unit test will be taking care of testing behaviour of another method. When you simply want to mock output of the another method then you can use stubs. If you want to ensure that another method is getting called along with mocking the output then you can set an exception for the method call. You can set multiple exceptions in a single test if multiple methods are getting called.
  3. Method call — At this point actual method which you want to test gets called. The method may print some output, update attributes in the database or return a value.
  4. Assertion — Assertions are used to verify the behaviour of the method. This is not required when the method is not returning any value. In case the method is returning results or setting attributes in the database then assertion is required.

When you have conditional branching in your method it's good to group tests together as this will make it easy to read tests and add new tests when you update your method to accommodate new requirements.

Let's assume that you have the following code class person.rb:

class Person < ApplicationRecord
AGE_ELIGIBLE_FOR_VOATING
= 18
attr_accessor :birth_date

def eligible_for_voting?
age >= AGE_ELLIGIBALE_FOR_VOATING
end

def age
# Logic to calculate age
end
end

Person class includes two methods - age and eligible_for_voting? . The age method calculates the age of the person using birth_date. Another method eligible_for_voting? figures out the eligibility of a person for voting using age.

You will be writing a separate unit test for the age method to assert if it's calculating age correctly. The mocks/exception part described above is not required in this spec as the age method does not call any other method of the same or different class.

eligible_for_voting? method is using the age method internally. In this case, you don’t need to test the logic of calculating age in the specs of this method. Instead, stub the response of the age method. Use stubbed age in further logic and test functionality written in the eligible_for_voting? method only. If anything goes wrong in the age method, that feedback will be provided by the unit test of the age method.

For the Person class, your spec file person_spec.rb will look something like the below:

require 'rails_helper'

describe Person, type: :model do
describe '#age' do
it 'should calculate and return age of person' do
# data setup, method call, assertion
end
end

describe '#eligible_for_voting?' do
it 'should return true when age is 18' do
Timecop.freeze('22/09/2018') do
person = Person.new(birth_date: Date.parse('22/09/2000'))

expect(person).to receive(:age).and_return(18)

eligible = person.eligible_for_voting?

expect(eligible).to be_truthy
end
end

it 'should return true when age is above 18' do
Timecop.freeze('22/09/2018') do
person = Person.new(birth_date: Date.parse('22/09/1995'))

expect(person).to receive(:age).and_return(23)

eligible = person.eligible_for_voting?

expect(eligible).to be_truthy
end
end

it 'should return false when age is below 18' do
Timecop.freeze('22/09/2018') do
person = Person.new(birth_date: Date.parse('22/09/2005'))

expect(person).to receive(:age).and_return(13)

eligible = person.eligible_for_voting?

expect(eligible).to be_falsey
end
end
end
end

Here I am using timecop gem to set certain dates in the system while running specs.

I have tried to group all related specs for a person’s voting eligibility in the same described block. This helps to increase the readability of specs. You can choose to group your specs differently which can make them more readable.

Also, you can merge the Method call and Assertion part when you are concerned only about the return value of the method. The spec will look like below:

it 'should return true when age is 18' do
Timecop.freeze('22/09/2018') do
person = Person.new(birth_date: Date.parse('22/09/2000'))

expect(person).to receive(:age).and_return(18)

expect(person.eligible_for_voting?).to be_truthy
end
end

Now you add logic for the age method and update the spec for the same.

Happy testing….

--

--