Testing Ruby with RSpec: The Complete Guide
[TOC]
Section 1: Introduction
Types of Tests
Three layers of tests:
- Unit tests: focus on individual units (a class, module, object or method)
- Integration tests:
- E2E tests: focus on a feature and its interaction with the entire system.
- specs hard to write, hard to troubleshoot and run slow.
We should have more unit tests, then integration tests, and then end-to-end tests.

I also like what Kent Dods says in https://testingjavascript.com:

Install RSpec and Start a Project
Install globally:
gem install rspec
Start a new project
mkdir rspec-course
cd rspec-course
rspec --init
The "init" will create .rspec and spec/spec_helper.rb
Test-Driven Development TDD
- Write your tests first, and the tests drive your development.
It's a top-down approach, where you first think about how you wanna use a piece of software, and then write the software.
Red -> Green -> Refactor
graph LR Red --> Green --> Refactor --> Red
What are the benefits of TDD?
It forces you to become a better developer. Simply practicing this thing is one of the best ways that I have become a better developer and matured as a programmer, especially in my object oriented thinking.
You don't have to read additional blog posts. This is something that you can do every day. Whenever you write code, just write your tests first.
The describe method - example group
The describe method creates an example group.
RSpec.describe 'Card' do
end
- RSpec is a module
- on that module we have the
describemethod - we give two arguments to the
describemethod:- the
'Card'string - a
do-endblock
- the
- inside the
do-endblock is where we will write all of our tests for theCard.
A test is also known as an "example". And "example group" is a set of related tests.
The it method - a single example
The describe creates an example group, the it method creates a single example.
RSpec.describe 'Card' do
it 'has a type' do
end
end
The idea is to describe how the software should behave, instead of saying how it should be implemented.
Note: the specify method has the exact same meaning as it.
The expect method
Doing assertions with expect.
RSpec.describe 'Card' do
it 'has a type' do
card = Card.new('Ace of Spades')
expect(card.type).to eq('Ace of Spades')
end
end
Assignment 1
Create an example group with a string argument of "math calculations".
Inside the group, create an example with a string argument of "does basic math".
Inside the example, write 4 mathematical assertions of your choice.
The expect method should receive a valid mathematical expression (for example, 3 + 4 or 5 * 3).
The eq method should compare the result fo the evaluation with the right answer.
RSpec.describe 'math calculations' do
it 'does basic math' do
expect(MyMath.plus(3, 4)).to eq(7)
expect(MyMath.minus(3, 4)).to eq(-1)
expect(MyMath.multiply(3, 4)).to eq(12)
expect(MyMath.divide(8, 4)).to eq(2)
end
end
Running the tests and reading failures
# run all tests in the spec/
rspec
# run a specific test file (aka example group)
rspec spec/card_spec.rb
# run a specific test (aka example)
rspec spec/card_spec.rb:2
Exercise
Create a class based on this test suite (example group):
RSpec.describe School do
it 'has a name' do
school = School.new('Beverly Hills High School')
expect(school.name).to eq('Beverly Hills High School')
end
it 'should start off with no students' do
school = School.new('Notre Dame High School')
expect(school.students).to eq([])
end
end
Reducing duplication - Before hook and instance variables
Consider this example group, with two tests, both of them instantiating a Card:
RSpec.describe Card do
it 'has a rank' do
card = Card.new('Ace', 'Spades')
expect(card.rank).to eq('Ace')
end
it 'has a suit' do
card = Card.new('Ace', 'Spades')
expect(card.suit).to eq('Spades')
end
end
In order to prevent duplication we're going to use an instance variable and assign a value to it in a before method:
RSpec.describe Card do
before do
@card = Card.new('Ace', 'Spades')
end
it 'has a rank' do
expect(@card.rank).to eq('Ace')
end
it 'has a suit' do
expect(@card.suit).to eq('Spades')
end
end
Reducing duplication - Helper methods
Another way to reduce duplication is to use a helper method. In this example, such method is called card:
RSpec.describe Card do
def card
Card.new('Ace', 'Spades')
end
it 'has a rank' do
expect(card.rank).to eq('Ace')
end
it 'has a suit' do
expect(card.suit).to eq('Spades')
end
end
Although it seems interesting, it can bring problems. Like the one explained below:
class Card
# rank can be changed
attr_accessor :rank, :suit
def initialize(rank, suit)
@rank = rank
@suit = suit
end
end
RSpec.describe Card do
def card
Card.new('Ace', 'Spades')
end
it 'has a rank and that rank can change' do
expect(card.rank).to eq('Ace')
card.rank = 'Queen' # this is actually create a new Card
expect(card.rank).to eq('Queen') # again, creating a new Card
end
end
Every time a card is used, it gives an impression that it's an object but it's actually a method, creating new Card objects everytime it's called.
Reducing duplication - the let method
Uses memoization to create an object.
let(:card) { Card.new('Ace', 'Spades') }
It uses lazy loading, therefore better than using before. Why? Because before runs before every single test, while using let makes that block to run only when the symbol passed to let is called.
The context method and nested describes
describecan be nestedcontextis a synonym fordescribe
Example:
RSpec.describe '#even? method' do
context 'with even number' do
it 'should return true' do
expect(4.even?).to eq(true)
end
end
context 'with odd number' do
it 'should return false' do
expect(5.even?).to eq(false)
end
end
end
before and after hooks
The code below is pretty descriptive. Just keep in mind that "context" is a synonym to "describe" and that "example" refers to each "it" block.
RSpec.describe 'before and after hooks' do
before(:context) do
puts 'Before context'
end
after(:context) do
puts 'After context'
end
before(:example) do
puts 'Before example'
end
after(:example) do
puts 'After example'
end
it 'is just a random example' do
expect(4 * 5).to eq(20)
end
it 'is just another random example' do
expect(3 - 2).to eq(1)
end
end