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_for
allows 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
subject
here 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_like
method 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)