RSpec Expectations Introduction

January 27, 2010

I still reading The RSpec Book, which is amazing! I´m doing a little review of what i´ve been reading about.

1.1 Creating Expectations

There are two methods available for checking expectations: should() and should_not().

1.2 Built-in Matchers

There are several matchers that can be used with should and should_not, which are divided into well-separated categories.

1.2.1 Equality: Equivalence and Identity

 cow.should == twin_cow
 cow.should === twin_cow
 cow.should eql(cow)
 cow.should equal(cow)

The == method is used to express equivalence and equal is used when you want the receiver and the argument to be the same object.

Note: Instead of using !=, you should use the should_not method!

1.2.2 Floating Point Calculations

 result.should be_close(3.14, 0.005)

When dealing with floating points, sometimes you expect 3.14 but 3.1415 is returned, which would be a pain to express on expectations if it wasn´t for the built in matcher be_close(), which takes two arguments: the floating point number you are expecting and the precision you require.

1.2.3 Regular Expressions

 result.should match(/this regexp/)

result.should =~ /this regexp/

This can be very useful when dealing with multiple-line expectations, instead of using the open file technique to compare contents.

1.2.4 Changes

 lambda {

User.create!(:role => "admin" )

}.should change{ User.admins.count }
 ## OR

 lambda {

User.create!(:role => "admin" )

}.should change{ User.admins.count }.to(1)

 ## OR

 lambda {

User.create!(:role => "admin" )

}.should change{ User.admins.count }.from(0).to(1)

This is really useful when working with database changes or changes to objects. Another way of writing this is by using the before_state -> change -> after_state technique, as follows:

 total.price.should == 0

buyer.inserts => 250)

total.price.should == 250

The matcher is change(), which takes a block with the object attribute/method and accepts the from(), to() or by() modifiers.

1.2.5 Errors

 field = => 20)

lambda {

field.remove(:players, 25)

}.should raise_error(NotEnoughPlayers,“attempted to remove more players than there is on field”)

Useful when needed to check for Exceptions. The matcher is raise_error and takes an ExceptionObject and/or a String/Regexp.

1.2.6 Throw

 speech = => 100)

100.times { speech.register }
 lambda {


}.should throw_symbol(:speech_full, 100)

When dealing with “errors that are not really exceptions”, you use catch and throw. Rspec can check if a throw has been called by using the throw_symbol matcher. It accepts 0,1 or 2 arguments. The first argument needs to be a Symbol and the second can be any Object that is thrown along.

1.3 Predicate Matchers

A Ruby predicate method is a method that ends with a “?” and returns a boolean value, like string.empty? or regexp.match? methods. Rspec allows us to write expectations to these methods in a beautiful, understandable way, instead of writing:

 a_string.empty?.should == true

We can write:

 a_string.should be_empty

1.3.1 The be_something method

When using a be_something matcher, RSpec removes the “be_”, appends a “?” and calls the resulting method in the receiver.

A very common construct of this method is be_true, which checks if the receiver is true (any object except false or nil) or false (false or nil).

1.4 Checking ownership

Sometimes you will want to check not the object itself, but something the object owns. In this case, RSpec allows you to write some beautiful sentences to check for owned objects.

1.4.1 The have_something method

 request_parameters.has_key?(:id).should == true
 # is the same as

request_parameters.should have_key(:id)

RSpec uses method_missing to convert anything that begins with have_something to has_something? and performs the checking.

1.4.2 The have() method {|p| == home_team }.length.should == 9

# is the same as

home_team.should have(9).players_on(field)

As have() does not respond to players_on(), it delegates to the receiver (home_team). It encourages the home_team object to have useful methods like players_on.

You can get a NoMethodError if the players_on method doesn´t exist, you can get another NoMethodError if the result of the players_on method doesn´t respond to size() or length() and if the size of the collection doesn´t match the expected size, you will get a failed expectation.

1.5 Checking Collections Themselves

Sometimes we create expectations about a collection itself and not about an owned collection. RSpec lets us use the have() method to express this as well, as in:

 a_collection.should have(10).items

items is just providing some meaning to the expectation.

1.5.1 Strings

Strings are not collections by definition but they respond to a lot of methods that collections do, like length() and size(). This allow us to use have() to expect a string of a specific length.

 “lucas”.should have(5).characters

characters is just providing meaning to the expectation as well.

1.5.2 Have() modifiers for precision

The have() method has some relatives that allow us to check for upper and lower conditions.

 work.should have_exactly(8).hours

basket.should have_at_least(5).items

auditorium.should have_at_most(100).people

1.6 Operator Expressions

There may be sometimes when you want to expect a value to be not an exact amount but something like greater than or less than. RSpec allows you to do this by using the regular operators from Ruby!

 number.should == 3

number.should be >= 2

number.should be <= 4

number should be > 0

1.7 Generated Descriptions

Sometimes the code within a expectation looks the same as the string used to describe it. In this cases, you can omit the string by using the specify() method, which is an alias of the it() method but reads better when there is no documentation string.

 describe “A new user” do
 specify { be_confused }

 #is the same as:
 describe “A new user” do
 it “should be confused” do be_confused

This should be used carefully and only in cases where the docstring and the code look exactly the same.

1.8 Subjects

Sometimes within an example group we want to use a subject on which we will develop the expectations. To create a subject you have to use the subject() method, which takes a code block creating the subject. Once you specify a subject, all the should() and should_not() methods can be called without a receiver as it will point to the subject in question.

 describe Rabbit
 subject { => 1) }
 specify { subject.should be_aged(1) }
 # or even better
 describe Rabbit
 subject { => 1) }
 it { should be_aged(1) }

You can also use implicit subjects when describing a class. RSpec will automatically create an instance of the described object, allowing us to write expectations like this:

 describe Rabbit
 it { should eat_carrots }

This should be used with caution though! You should not coerce your objects to fit into this model instead of writing it as you should really have written!

1.9 Matchers Table and Thoughts

Matcher Used to expect
==, ===, eql, equal (other) Equality between objects
be_close(other, precision) Floating Points
match, =~ (other) Regular Expressions
CodeBlock.should Change(&attribute)[.by(num)][.from(num).to(num)] Changes in attributes
CodeBlock.should raise_error([ErrorClass,[Error String/Regexp]]) Exceptions
CodeBlock.should throw_symbol([Symbol,[Object]]) Throws
be_xxx (like be_empty) Predicate methods (ending on ?)
have_xxx (like have_key) has_xxx? Methods
have(number)[.receiver_method] Number informations
be (==,>=,>,<=,<) Operators
  • The specify() method can be used when a docstring looks like the code within the expectation.
  • The subject() method takes a code block and can be used to specify a subject. Once specified, the subject becomes the receiver for every should and should_not.
  • Subjects can be automatically created when describing a class. RSpec calls the new() method to the object and use it as the subject to the following expectations.

4 Responses to “RSpec Expectations Introduction”

  1. Nice introduction.

    Just a typo:
    a_string.empty? should == true
    should be
    a_string.empty?.should == true


  2. Muthu Says:

    great article.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: