When writing tests with RSpec, you may need code to run at certain times. You also will need certain objects to test. This blog serves as a guide for using hooks, the let method, subject and shared examples.
Hooks
IMPORTANT NOTE: Hooks require the use of instance variables in order to make objects available to examples.
1.) Before
config.before(:suite) do # suite Scope
# You'd put this in your spec_helper file.
# This code runs one time at the very start. You can use it to
# run any setup before running any tests.
end
before(:context) do # context scope
# The before block takes an argument like: (:context, :example)
# context scope refers to the describe or context blocks.
# So once before every describe/context block, this code will run.
end
before(:example) do # example Scope
# This code will run once before every example. So before every
# 'it' block, this code will run.
end
2.) After -> Is exactly like the before block, except it is called in a different order.
This is what the Relishapp documentation for RSpec states about the order in which hooks are called:
before and after blocks are called in the following order:
before :suite
before :context
before :example
after :example
after :context
after :suite
As you can see, before runs in order from :suite, :context to :example. after runs in reverse from :example, :context to :suite.
- More on Before/After blocks: Before/After Rspec Hooks
3.) Around
around runs after before and after. With around, you set a block argument. It is like having both a before and after block. This allows us to do everything in one step.
around(:example) do |example| # example Scope
puts "I run before example.run" # This would be code run before the example
example.run # this will run the example
puts "I run after example.run" # This would be code run after the example
end
it "runs in the middle" do
puts "example.run runs the example test"
end
The output for running these tests would be:
I run before example.run
example.run runs the example test
I run after example.run
- More on Around blocks: RSpec Around Hook
IMPORTANT NOTE: Hooks require the use of instance variables in order to make objects available to examples.
The let method & subject
The let method is a helper method. It takes one argument which is the name of the method you would like to use:
let(:person)
Then you use a block to set an instance variable object you will use in other examples:
let(:person) { Person.new }
This is a method. The above let method is doing exactly what this is:
before(:context) do
def Person
@person ||= Person.new
end
end
Remember that hooks need to use instance variables so that the object can be passed in to the examples.
Now you can use one instance of a person before every example.
subject
The subject method is exactly like let, except for a few things:
1.) When using a class name for the example group(the describe block), you don't have to define the name of the method using let. The name of the method will be the argument of the describe block, so you don't have to name the method:
subject { Person.new }
2.) If you are keeping it simple, subject already made a helper method for you. So you wouldn't have to define the object either.
describe Person do
context "attributes" do
it "#name" do
subject.name = "Ethan"
expect(subject.name).to eq("Ethan")
end
end
end
See how I didn't have to define subject at all? The argument name of the example group was Person. subject already created a helper method with a simple new instance, Person.new.
Shared Examples
What if you have code that is repeated in multiple tests?
There are two things you can use:
shared_examples_for()it_behaves_like()
In my previous RSpec blog I used my personal project "One Piece", which had two classes share the same tests. Here is how I used shared_examples_for and it_behaves_like
I created a separate file called character_devil_fruit.rb and in it:
shared_examples_for("character_devil_fruit") do
it "Have attributes for :bio, :start_i, :end_i" do
expect(subject).to respond_to(:bio, :start_i, :end_i)
end
it ":start_i and :end_i must be integers" do
subject.start_i = 2
subject.end_i = 4
expect(subject.start_i).to be_an(Integer)
expect(subject.end_i).to be_an(Integer)
end
it "includes Instance Methods Module" do
expect(described_class.included_modules).to include(InstanceMethods)
end
end
-
shared_examples_forallows you to contain code used in multiple spec tests. It takes one argument, which is the block name. - Which is also why we can use
subjecthere so that we don't hard-code our class names.
And in character_spec.rb:
require_relative '../../lib/classes/character.rb'
require 'shared_examples/character_devil_fruit'
describe Character do
context "attributes" do
subject { Character.new("Luffy", "https://onepiece.fandom.com/wiki/Monkey_D._Luffy") } # Subject is just the starting describe argument name
it "Instantiate with a name & URL" do
expect(subject).to have_attributes(:name => "Luffy", :url => "https://onepiece.fandom.com/wiki/Monkey_D._Luffy")
end
it_behaves_like('character_devil_fruit')
it ":name, :url & :bio must be strings" do
subject.bio = "Kaizoku-ō ni ore wa naru!"
expect(subject.name).to be_an(String)
expect(subject.url).to be_an(String)
expect(subject.bio).to be_an(String)
end
end
context 'class Methods' do
it '.all method which will record all instances of the class' do
expect(Character.all).to be_an(Array)
end
end
end
- As you can see in the middle of the file, there is the
it_behaves_likemethod which takes an argument of the shared example block argument name - Doing this will essentially be like 'copying' and 'pasting' the code in this file.
Top comments (0)