Executable Examples
RSpec provides a Domain Specific Language with which you can express executable examples of the expected behaviour of a system.
Imagine that you were talking to a customer requesting software for her bank. Part of that conversation might well look like this:
You: Describe an account when it is first created. Customer: It should have a balance of $0.
Here’s how we express that conversation in RSpec:
describe Account, " when first created" do
it "should have a balance of $0" do
...
end
end
When you execute this example, RSpec can provide a report like this:
Account, when first created - should have a balance of $0
So we use RSpec to #describe Behaviour of a system using Examples
of how #it should work.
A Bit More Detail
Here’s how the example might look with some more code filled in:
describe Account, " when first created" do
before do
@account = Account.new
end
it "should have a balance of $0" do
@account.balance.should eql(Money.new(0, :dollars))
end
after do
@account = nil
end
end
The #describe method returns a Behaviour object, which represents
a particular behaviour of the system that you wish to describe. Technically,
a Behaviour is the equivalent of a fixture in xUnit-speak. It
is a metaphor for the context in which you will execute your example – a set of
known objects in a known starting state.
The #it method returns an Example object, which represents
an example of the behaviour your are trying to describe.
The #before and #after methods are just
like #setup and #teardown in xUnit. You use them to
set up the state before each example is executed, and tear down any
resources after each example.
#before and #after
You can use #before and/or #after to define
code that executes before and after each example or only once per Behaviour:
describe Thing do
before(:all) do
# This is run once and only once, before all of the examples
# and before any before(:each) blocks.
end
before(:each) do
# This is run before each example.
end
before do
# :each is the default, so this is the same as before(:each)
end
it "should do stuff" do
...
end
it "should do more stuff" do
...
end
after(:each) do
# this is before each example
end
after do
# :each is the default, so this is the same as after(:each)
end
after(:all) do
# this is run once and only once after all of the examples
# and after any after(:each) blocks
end
end
Warning: The use of #before(:all) and #after(:all) is
generally discouraged because it introduces dependencies between the Examples.
Still, it might prove useful for very expensive operations if you know what you are doing.
Helper Methods
You can write helper methods directly within a Behaviour:
describe "..." do
it "..." do
helper_method
end
def helper_method
...
end
end
Reusable Helper Methods
You can include helper methods in multiple Behaviours by expressing them within a module, and then including that module in your Behaviour:
module AccountExampleHelperMethods
def helper_method
...
end
end
describe "A new account" do
include AccountExampleHelperMethods
before do
@account = Account.new
end
it "should have a balance of $0" do
helper_method
@account.balance.should eql(Money.new(0, :dollars))
end
end
Shared behaviors
You can create shared behaviors and include those behaviors into other behaviors.
Suppose you have some behavior that applies to all editions of your product, both large and small.
First, factor out the “shared” behavior:
describe "AllEditions", :shared => true do
it "should behave like all editions" do
end
end
then when you need define the behavior for the Large and Small editions, reference the shared behavior using the ‘it_should_behave_like’ method.
describe "SmallEdition" do
it_should_behave_like "AllEditions"
it "should also behave like a small edition" do
end
end
describe "LargeEdition" do
it_should_behave_like "AllEditions"
it "should also behave like a large edition" do
end
end
it_should_behave_like will search for a Behavior by its description string, in this case, “AllEditions”
All of the following are included from a shared behavior:
- before(:all)
- before(:each)
- after(:each)
- after(:all)
- any included modules
Shared behaviors may not extend classes.
Multiple shared behaviors may be referenced in one (non-shared) behaviour.
Shared behaviours may be included in other shared behaviours:
describe "All Employees", :shared => true do
it "should be payable" do
@employee.should respond_to(:calculate_pay)
end
end
describe "All Managers", :shared => true do
it_should_behave_like "All Employees"
it "should be bonusable" do
@employee.should respond_to(:apply_bonus)
end
end
describe Officer do
before(:each) do
@employee = Officer.new
new
it_should_behave_like "All Managers"
it "should be optionable" do
@employee.should respond_to(:grant_options)
end
end
$ spec officer_spec.rb
Officer
- should be payable
- should be bonusable
- should be optionable
Pending Examples
Beginning with rSpec-1.0, you may omit the block on an an example:
it "should do something"
the summary report lists that example as not implemented.
37 examples, 0 failures, 1 not implemented
However, if you wish to have a body for your example, but still have the example be marked as pending, use the #pending method:
it "should do something" do
pending("for some reason")
true.should == true
end
Additionally, suppose you have a behaviour that exposes a known bug in the system. For reasons beyond your control this bug will not be fixed immediately, but you don’t want it failing your build. However, you would like to be notified when the bug has been fixed.
For this scenario, #pending optionally accepts a block.
it "should do something" do
pending("for obvious reasons") do
true.should == false
end
end
If this block throws any exception, the example is still treated as pending. However, if the block does not throw any exceptions, #pending will raise a Spec::Expectations::ExpectationNotMetError alerting you to the fact that something has changed.