We are continuing an introduction to RSpec, a testing tool for Ruby. After covering the very basics in the first part of this series, we will now explore other important and frequently used features of RSpec: use of subject , let helper method, before hooks and exception handling. Along the way we will encounter some more matchers and how to compose them.

After this tutorial you should have a solid background to start using RSpec in your Ruby projects and apply its features in an idiomatic way.

Test Subjects

Let’s say that we need to write a simple program for runners who need to log their runs and are interested in seeing some weekly statistics. Basic information about a run includes distance, duration and when it happened.

We know that there should be a class Run with three attributes, which we should be able to initialize easily. We may start by describing it as follows:

describe Run do describe " attributes " do subject do Run . new ( :duration => 32 , :distance => 5.2 , :timestamp => " 2014-12-22 20:30 " ) end it { is_expected.to respond_to( :duration ) } it { is_expected.to respond_to( :distance ) } it { is_expected.to respond_to( :timestamp ) } end end

In this example we declare a subject to be an instance of class Run . The reason why we define it is that we have multiple test examples that work with the same test subject. RSpec understands it as an object which should respond to (in core Ruby sense) a number of methods, such as duration . The expectation is using RSpec’s built-in respond_to matcher.

The one-line syntax shown above is convenient when you can avoid duplication between a matcher and the string that documents the test example. If this were not possible, we would need to write the examples above like this:

it " responds to '#duration' " do expect(subject).to respond_to( :duration ) end

In fact, if we our objects can be initialized without parameters, we can make it even shorter:

describe Run do it { is_expected.to respond_to( :duration ) } end

This is because we passed the class to the describe block and RSpec has already initialized a subject in the global example group to be subject { Run.new } . This is the basis of what drives the code which you can frequently see in Rails applications:

describe Post do it { is_expected.to validate_presence_of( :title ) } end

Subjects can also be referenced explicitly, via subject :

describe Run do subject do Run . new ( :duration => 32 , :distance => 5.2 , :timestamp => " 2014-12-22 20:30 " ) end describe " #timestamp " do it " returns a DateTime " do expect(subject.timestamp).to be_a( DateTime ) end end end

However, we recommend that you do not use subject like this. There are other ways which reveal the intent of code more clearly, as we will see below.

Before Hooks

In order to write a test, we often need to bring the world to a certain state first. For example, let’s say that we are describing a method that should return the total number of logged runs: Run.count . This method can also optionally receive a parameter to limit the scope to one week. Before we call it, we need to log a number of runs first.

RSpec’s before hook is a convenient way to structure code which should run before every example, as in the following spec:

describe RunningWeek do describe " .count " do context " with 2 logged runs this week and 1 in next " do before do 2 .times do Run .log( :duration => rand ( 10 ), :distance => rand ( 8 ), :timestamp => " 2015-01-12 20:30 " ) end Run .log( :duration => rand ( 10 ), :distance => rand ( 8 ), :timestamp => " 2015-01-19 20:30 " ) end context " without arguments " do it " returns 3 " do expect( Run .count).to eql( 3 ) end end context " with :week set to this week " do it " returns 2 " do expect( Run .count( :week => " 2015-01-12 " )).to eql( 2 ) end end end end end

Note how we have not only managed to avoid duplication, but also to have nicely readable test examples which contain only the essence of the test.

When you write before , it is the equivalent of writing before(:each) , which means “run this code before each example”. You can also say before(:all) which would run the code only once for the given context. If you need, you can also define an after hook, with the same variants. You can read more about hooks in RSpec documentation.

Let Helper

RSpec’s let helper is a way to define all dependent objects for test examples. If you need to reference the same “thing” in more than one example, and it cannot be made a subject , that is a good use case for let .

The code that is placed inside a let block is lazily evaluated: it is executed only the first time a test example calls it and is cached for further calls in the same example. If you need to force the method to be invoked every time, use let! .

Let’s say we realized that our code for providing weekly running statistics would be placed in a RunningWeek class.

describe RunningWeek do let( :monday_run ) do Run . new ( :duration => 32 , :distance => 5.2 , :timestamp => " 2015-01-12 20:30 " ) end let( :wednesday_run ) do Run . new ( :duration => 32 , :distance => 5.2 , :timestamp => " 2015-01-14 19:50 " ) end let( :runs ) { [monday_run, wednesday_run] } let( :running_week ) { RunningWeek . new ( Date .parse( " 2015-01-12 " ), runs) } describe " #runs " do it " returns all runs in the week " do expect(running_week.runs).to eql(runs) end end describe " #first_run " do it " returns the first run in the week " do expect(running_week.first_run).to eql(monday_run) end end describe " #average_distance " do it " returns the average distance of all week's runs " do expect(running_week.average_distance).to be_within( 0.1 ).of( 5.4 ) end end end

Contrast this code with the example for the before hook, where we needed to prepare some data but did not care about it later. In this spec we need to both prepare some data and easily reference it in test examples.

Also, note the use of composed matchers in the last example ( be_within(...).of(...) ). RSpec’s matchers can be composed, which helps improve code readability. If you are interested in seeing the full list of matchers that exist and can be composed, browse the documentation of classes and methods in RSpec::Matchers::BuiltIn .

Instance Variables in Before Hooks vs Let

At this point you may be wondering why not just use instance variables in before blocks instead of let :

describe RunningWeek do before do @monday_run = Run . new (...) end end

There are actually three approaches to the extent of the use of let :

Using let to define all object dependencies and keep the body of examples minimal. Use it occasionally to avoid duplication by defining “variables” when in need to reference the same thing in multiple test examples, but not much more. Not using it at all; relying on instance variables in before hooks only.

We encourage you to adopt the first approach, combined with additional data setup in a before hook when necessary. It results in very readable code, is less error-prone (typos in instance variables do not raise exceptions) and may give better performance due to lazy evaluation. RSpec maintainer Myron Marston recommends it as well.

Exception Handling

We often need to specify that a given method or block of code should raise an exception. For this purpose you can use the raise_error matcher (or its equivalent, raise_exception ):

describe RunningWeek do describe " initialization " do context " given a date which is not a Monday " do it " raises a 'day not Monday' exception " do expect { RunningWeek . new ( Date .parse( " 2015-01-13 " ), []) }.to raise_error( " Day is not Monday " ) end end end end

In general you can narrow the expectation to be based on the error message and/or class.

If your code raises an exception and it is not followed by this matcher, the spec will fail.

Wrapping Up

After reading this and the first RSpec tutorial, you should have a solid foundation to start using RSpec in your Ruby projects. If you are interested in a bit more advanced topics of object mocking and method stubbing, continue reading the series in Mocking With RSpec: Doubles and Expectations.

P.S. Would you like to learn how to build sustainable Rails apps and ship more often? We’ve recently published an ebook covering just that — “Rails Testing Handbook”. Learn more and download a free copy.