Table of Contents
- Before and After hooks
- Let
- Subject
- Mocks and Stubs
- ex1
- ex2
- ex1
- ex2
- ex3 (Arguments)
- ex4 (any_instance_of)
Before and After hooks
You can specify when to run a set of code by using hooks.
The most common hooks in Rspec are before and after hooks.
before(:example) #run before each example
before(:each) #run before each example
before(:context) # run one time only, before all of the examples in a group
before(:all) # run one time only, before all of the examples in a group
after(:example) # run after each example
after(:each) # run after each example
after(:context) # run one time only, after all of the examples in a group
after(:all) # run one time only, after all of the examples in a groupbefore and after blocks are called in the fallowing order.
before :context(all)
before :example(each)
after :example(each)
after :context(all)Let’s see some examples below.
describe 'Post' do
before(:each) do
puts 'before(:each)'
end
before(:all) do
puts 'before(:all)'
end
after(:each) do
puts 'after(:each)'
end
after(:all) do
puts 'after(:all)'
end
it 'tests hooks' do
puts 'test case1'
end
it 'tests hooks' do
puts 'test case2'
end
endThe result of the above code would be like this.
before(:all)
before(:each)
test case1
after(:each)
before(:each)
test case2
after(:each)
after(:all)With before and after hooks, you can create, delete or update the data very flexibly.
describe 'Hooks' do
before(:all) do
@obj1 = 'string'
end
it 'tests before(all)' do
expect(@obj1).to include('s')
end
endLet
let helps DRY up your tests. If you write let(:foo){ ... }, you can retribute values that are defines in { ... } from foo.
Here is what I mean.
describe 'Let' do
let(:post) { Post.new(title: 'Ruby', author: 'J') }
it 'tests let' do
puts post.title #=> Ruby
puts post.author #=> J
end
endAs you can see in the code above, post has { title: 'Ruby, author: 'J' } and you can retrieve those values with post.title and post.author.
Let is lazily evaluated
One thing you should keep in your mind about let is that it is lazily evaluated that means it is not evaluated until the first time it’s invoked. You can use let! to force the method’s invocation before each example.
describe 'Let' do
let(:post) { Post.new(title: 'Ruby', author: 'J') } #let is not evaluated here.
it 'tests let' do
puts post.title #=> Ruby #let(:post) is evaluated here.
puts post.author #=> J
end
endThe value will be cached across multiple calls in the same example but not across examples.
$count = 0
describe "let" do
let(:count) { $count += 1 } #Add 1 to count every time let is evaluated.
it "memoizes the value" do
count.should == 1
count.should == 1 #The value will be cached across multiple calls in the same example.
end
it "is not cached across examples" do
count.should == 2 #The value is NOT cached across examples
end
endPractical examples of “let”
I am going to compare two sets of code to show how let DRYs your code up.
In Example①, I’ll write specs without let, while in Example②, I’ll test the same things with let.
Example①
describe 'Post' do
context 'When the author is Jack' do
before(:all) do
post = Post.new(title: 'Ruby', author: 'Jack')
end
it 'was posted by Jack' do
expect(post.author).to eq 'Jack'
end
end
context 'When the author is Mike' do
before(:all) do
post = Post.new(title: 'Ruby', author: 'Mike')
end
it 'was posted by Mike' do
expect(post.author).to eq 'Mike'
end
end
endIn Example①, there are two before blocks that virtually do the same thing and it seems very redundant. So I’ll DRY it up in the Example②.
Example②
describe 'Post' do
let(:post) { Post.new(params) }
let(:params) { { title: 'Ruby', author: author } }
context 'When the author is Jack' do
let(:author) { 'Jack' }
it 'was posted by Jack' do
expect(post.author).to eq 'Jack'
end
end
context 'When the author is Mike' do
let(:author) { 'Mike' }
it 'was posted by Mike' do
expect(post.author).to eq 'Mike'
end
end
endIn Example②, I defined let before two example-groups and only changed the author of post in each context. This prevents you from defining essentially the same thing over and over again.
Subject
The subject keyword refers to the object being tested. For instance, Post is the subject of this example group in the code below.
describe Post do
#tests
endImplicitly defined subject
By default, If the first argument to the outermost example group is a class, RSpec implicitly creates an instance of that class and assigns it to the subject.
describe Post do
it 'is implicitly instatiated by Rspec' do
expect(subject).to be_an(Post) #You can refer to the object as 'subject'
end
endIn the code above, subject is used in the example even though it is not defined anywhere in the code. This is because Rspec implicitly created an instance from Post and assigned it to subject.
Explicit subject
subject also can be defined explicitly. Readers can see how it’s instantiated.
describe Post do
subject { Post.new }
it 'tests subject' do
expect(subject).to be_a(Post)
end
endYou can give the subject a name.
describe Post do
subject(:post) { Post.new }
it 'tests subject' do
expect(post).to be_a(Post)
end
endEven if you name the subject, you can still refer to it anonymously:
describe Post do
subject(:post) { Post.new }
it 'tests subject' do
expect(subject).to be_a(Post)
end
endOne-liner syntax
RSpec supports a one-liner syntax for setting an expectation on the
subject. With Rspec one-liner syntax, you can make code like Example③ shorter like Example④.
Example③
describe Post do
it 'is implicitly instantiated by Rspec' do
expect(subject).to be_an(Post)
end
endExample④
describe Post do
it { is_expected.to be_a(Post) }
endYou can see more information about subject and one-liner syntax here
Mocks and Stubs
Test Doubles
A test double is an object that stands in for another object in your system during a code
example.
# ex1
book = double("book", name: 'bookName')
book #=> #<Double "book">
book.name #=> bookName
# ex2
book = instance_double("Book", pages: 250)
book #=> #<InstanceDouble(Book) (anonymous)>
book.pages #=> 250Method Stubs
A method stub is an instruction to an object (real or test double) to return a
known value in response to a message.
# ex1
book = double("Book")
allow(book).to receive(:sell).and_return("The book is sold")
book.sell #=> The book is sold"
# ex2
client = double("ClamAV::Client", execute: [response]) # bouble
allow(ClamAV::Client).to receive(:new).and_return(client) # stub
# ex3 (Arguments)
allow(obj).to receive(:message).with('an argument') { ... }
obj.stub(:message).with('an argument') { ... }
obj.stub(:message).with('more_than', 'one_argument') { ... }
# ex4 (any_instance_of)
allow_any_instance_of(Object).to receive(:foo).and_return(true)
allow_any_instance_of(Object).to receive(:foo).with(:param_one, :param_two).and_return(:result_one)Check out the links below for more information about mocks and stubs.