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 Product.new(:price => 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 = SoccerField.new(:players => 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 = Speech.new(:seats => 100)
100.times { speech.register Person.new }
lambda {
speech.register Person.new
}.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
field.players.select {|p| p.team == 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 { User.new().should be_confused }
end
#is the same as:
describe “A new user” do
it “should be confused” do
User.new.should be_confused
end
end
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 { Rabbit.new(:age => 1) }
specify { subject.should be_aged(1) }
end
# or even better
describe Rabbit
subject { Rabbit.new(:age => 1) }
it { should be_aged(1) }
end
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 }
end
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.
January 27, 2010 at 4:41 pm
Nice introduction.
Just a typo:
a_string.empty? should == true
should be
a_string.empty?.should == true
Cheers!
January 27, 2010 at 4:44 pm
oops! just got it fixed!
thanks a lot!
November 8, 2010 at 9:00 am
string.should be_empty
:)
August 10, 2010 at 2:08 pm
great article.