Chapter 18. RSpec on Rails

 

I do not think there is any thrill that can go through the human heart like that felt by the inventor as he sees some creation of the brain unfolding to success.

 
 --Nikola Tesla

RSpec is a Ruby domain-specific language for specifying the desired behavior of a Ruby application. Its strongest appeal is that RSpec scripts tend to be very readable, letting the authors express their intention with greater readability and fluidity than is achievable using Test::Unit’s methods and assertions.

RSpec on Rails, a drop-in replacement for the Rails testing subsystem, was released by the RSpec team in late 2006. It supplies verification, mocking, and stubbing features customized for use with Rails models, controllers, and views. I’m happy to say that I’ve been using the RSpec plugin for my Rails projects every day since it was released, and have never needed to touch Test::Unit for anything significant again.[1] RSpec is simply that good.

Introduction to RSpec

Since RSpec scripts (or specs) are so readable, I can’t really think of a better way of introducing you to the framework than to dive into some actual specs. Along the way, I’ll highlight the key concepts that you need to know.

Listing 18.1 is part of a real-world RSpec script defining the behavior of a CreditCard model class.

Example 18.1. A Description Block from Monkeycharger’s CreditCard Spec[2]

 1  describe "A valid credit card" do
 2     before(:each) do
 3       @credit_card = generate_credit_card
 4     end
 5
 6     it "should be valid after saving" do
 7       @credit_card.save
 8       @credit_card.should be_valid
 9     end
10  end

RSpec scripts are collections of behaviors, which in turn have collections of examples. Line 1 uses the describe method to create a Behavior object under the covers. The behavior sets the context for a set of specification examples, and you should pass it a sentence fragment that accurately describes the context you’re about to specify.

The before method on line 2 (and its companion after) are akin to the setup and teardown methods of xUnit frameworks like Test::Unit. They are used to set up the state as it should be prior to running an example, and if necessary, to clean up the state after the example has run. This particular behavior did not require an after block. (For brevity, the source of the generate_credit_card method on line 3 is not included in this listing. It is simply a factory method that returns instances of CreditCard preset with known and overridable attribute values. We’ll learn more about using helper methods to write more readable spec code later in the chapter.)

The it method on line 6 is used to create an Example object, and you also give it a description. The idea is to complete the thought that was started in the describe method, so that it forms a complete sentence. Our example reads “A valid credit card should be valid after saving.” See it?

Should and Expectations

Moving on, line 7 of Listing 18.1 invokes save on the credit card object, and since it’s an ActiveRecord model, we know that will get it validated. So all we have left is to verify that the credit card instance is valid. Rather than xUnit-style assertions, RSpec introduces some funky DSL-ish syntax to do verification, based on a pair of methods called should and should_not.

RSpec mixes should and should_not into the base Ruby Object class at runtime so that they are available on all objects. They expect to receive Matcher objects, which you generate using RSpec expectation syntax.

@credit_card.should be_valid

There are several ways to generate expectation matchers and pass them to should (and should_not):

receiver.should(matcher)                 # the simplest example
# Passes if matcher.matches?(receiver)

receiver.should == expected #any value
# Passes if (receiver == expected)

receiver.should === expected #any value
# Passes if (receiver === expected)

receiver.should =~ regexp
# Passes if (receiver =~ regexp)

The process of learning to write expectations is probably one of the meatier parts of the RSpec learning curve. One of the most common idioms is “should equal,” akin to Test::Unit’s assert_equal assertion. This is how you would rewrite the “credit card should be valid” assertion using should equal syntax:

@credit_card.valid?.should == true

The valid? method returns true or false and according to our spec, it should be true. Now, why didn’t we write the expectation that way to begin with? Simply because that question-mark-and-period combo is ugly. It works, but it’s not as elegant or readable as saying: should be_valid. There is no predefined be_valid method in RSpec—it is an arbitrary predicate.

Predicates

Thanks to the magic of method_missing, RSpec can support arbitrary predicates, that is, it understands that if you invoke something that begins with be_, then it should use the rest of the method name as a pointer to a Boolean attribute of the target object.

The simplest hard-coded predicate-style matchers are used to assert Boolean and nil target values:

target.should be_true
target.should be_false
target.should be_nil
target.should_not be_nil

The arbitrary predicate matchers can assert against any Boolean target, and even support parameters!

collection.should be_empty #passes if target.empty?
target.should_not be_empty         # passes unless target.empty?
target.should_not be_under_age(13) # passes unless
target.under_age?(13)

As an alternative to prefixing arbitrary predicate matchers with be_, you may choose from the indefinite article versions be_a_ and be_an_, making your specs read much more naturally:

"a string".should be_an_instance_of(String)
3.should be_a_kind_of(Fixnum)
3.should be_a_kind_of(Numeric)
3.should be_an_instance_of(Fixnum)
3.should_not be_instance_of(Numeric) #fails

The cleverness (madness?) doesn’t stop there. RSpec will even understand have_ prefixes as referring to predicates like has_key?:

{:foo => "foo"}.should have_key(:foo)
{:bar => "bar"}.should_not have_key(:foo)

RSpec has a number of expectation matchers for working with classes that implement module Enumerable. You can specify whether an array should include a particular element, or if a string contains a substring.

[1, 2, 3].should include(1)
[1, 2, 3].should_not include(4)
"foobar".should include("bar")
"foobar".should_not include("baz")

You get a slick bit of syntactic sugar for testing the length of collections:

[1, 2, 3].should have(3).items

What if you want to specify the length of a has_many collection? “Schedule.days.should have(3).items” is admittedly quite ugly. RSpec gives us some more sweetness here as well.

schedule.should have(3).days  # passes if schedule.days.length == 3

Custom Expectation Matchers

When you find that none of the stock expectation matchers provide a natural-feeling expectation, you can very easily write your own. All you need to do is write a Ruby class that implements the following four methods (only two are required):

matches?(actual)
failure_message
negative_failure_message #optional
description #optional

The example given in the RSpec API documentation is a game in which players can be in various zones on a virtual board. To specify that a player bob should be in zone 4, you could write a spec like this:

bob.current_zone.should eql(Zone.new("4"))

However, it’s more expressive to say one of the following, using the custom matcher in Listing 18.2:

bob.should be_in_zone("4") and bob.should_not be_in_zone("3")

Example 18.2. BeInZone Custom Expectation Matcher Class

class BeInZone
  def initialize(expected)
    @expected = expected
  end
  def matches?(target)
    @target = target
    @target.current_zone.eql?(Zone.new(@expected))
  end

  def failure_message
    "expected #{@target.inspect} to be in Zone #{@expected}"
  end

  def negative_failure_message
    "expected #{@target.inspect} not to be in Zone #{@expected}"
  end

end

In addition to the matcher class you would also need to write the following method so that it’d be in scope for your spec.

def be_in_zone(expected)
  BeInZone.new(expected)
end

This is normally done by including the method and the class in a module, which is then included in your spec.

describe "Player behaviour" do
  include CustomGameMatchers
  ...
end

Or you can include helpers globally in a spec_helper.rb file required from your spec file(s):

Spec::Runner.configure do |config|
  config.include(CustomGameMatchers)
end

Note that you don’t have to worry about Behavior and Example object instances while you’re writing RSpec scripts. (They are only used internally by the framework.)

Multiple Examples per Behavior

The Monkeycharger example presented in Listing 18.1 is pretty simple and only has one example, but only because I wanted a simple introduction to the most basic concepts of RSpec.

A behavior usually has more than one example in it. It’s easiest to just show you some more code from Monkeycharger. Listing 18.3 is the next describe block of that CreditCard spec and has five examples.

Example 18.3. Another Description Block from Monkeycharger’s CreditCard Spec

describe CreditCard do
  it "should have a valid month" do
    card = generate_credit_card(:month => 'f')
    card.errors.on(:month).should == "is not a valid month"
  end

  it "should have a valid year" do
    card = generate_credit_card(:year => 'asdf')
    card.errors.on(:year).should == "is not a valid year"
  end

  it "date should not be in the past" do
    past_month = (Date.today << 2)
    card = generate_credit_card(:year  => past_month.year,
                                :month => past_month.month)
    card.should_not be_valid
  end

  it "should have two words in the name" do
    card = generate_credit_card(:name => "Sam")
    card.errors.on(:name).should == "must be two words long."
  end

  it "should have two word last_name if name is three words long" do
    card = generate_credit_card(:name => "Sam Van Dyk")
    card.last_name.should == "Van Dyk"
  end

  it "should have one word first_name if name is three words long" do
     card = generate_credit_card(:name => "Sam Van Dyk")
     card.first_name.should == "Sam"
  end

end

Even if you don’t know much about credit cards (or RSpec for that matter), you should still be able to read through this spec without too much of a problem.

Plain old RSpec scripts usually need a string passed to the describe method. The spec in Listing 18.3 happens to be an ActiveRecord model spec based on the RSpec on Rails plugin, so it’s okay to pass it an ActiveRecord model class instead of a description string. (More on that later when we get into Rails specifics. Right now we’re still just getting through the basics of RSpec.)

Shared Behaviors

Often you’ll want to specify multiple behaviors that share a lot of the same behavior. It would be silly to type out the same code over and over. Most programmers will extract the common code into individual methods. However, the problem is that an RSpec behavior contains many pieces:

  • before(:all)

  • before(:each)

  • after(:each)

  • after(:all)

  • all of the expectations

  • any included modules

Even with good refactoring, you’ll end up with lots of duplication. Fortunately RSpec lets us take advantage of shared behaviors. Shared behaviors aren’t run individually, but rather are included into other behaviors. We do this by passing the :shared => true option to describe.

Let’s say we want to specify two classes, Teacher and Student. In addition to their unique behavior, they have some common behavior that we’re interested in. Instead of specifying the same behavior twice, we can create a shared behavior and include it in each class’s specification.

describe "people in general", :shared => true do
  it "should have a name" do
    @person.name.should_not be_nil
  end

  it "should have an age" do
    @person.age.should_not be_nil
  end
end

Where does the @person instance variable come from? We never assigned it anywhere. It turns out this spec won’t run because there’s nothing to run yet. Shared behaviors are just used to factor out common behavior specifications. We need to write another spec that uses the shared behavior.

describe Teacher do
  before(:each) do
    @person = Teacher.new("Ms. Smith", 30, 50000)
  end

  it_should_behave_like "people in general"

  it "should have a salary" do
    @person.salary.should == 50000
  end
end

The it_should_behave_like takes a string argument. RSpec then finds the shared behavior with that name and includes it into the Teacher specification.

We can do the same thing with Student.

describe Student do
  before(:each) do
    @person = Student.new("Susie", 8, "pink")
  end

  it_should_behave_like "people in general"

  it "should have a favorite color" do
    @person.favorite_color.should == "pink"
  end
end

Passing —format specdoc (or -f s in abbreviated form) to the spec command shows that the shared behavior is indeed included in the individual class specifications.

Teacher
- should have a name
- should have an age
- should have a salary

Student
- should have a name
- should have an age
- should have a favorite color

It’s important to note that, as of this writing, RSpec runs the before and after methods in the order they’re defined in the spec. You can see this by adding an output statement to each one.

describe "people in general"
  before(:each) do
    puts "shared before()"
  end

  after(:each) do
    puts "shared after()"
  end
  ...
end

describe Teacher do
  before(:each) do
    puts "teacher before()"
    @person = Teacher.new("Ms. Smith", 30, 50000)
  end

  after(:each) do
    puts "teacher after()"
  end

  it_should_behave_like "people in general"
  ...
end

This will give us the following output:

teacher before()
shared before()
teacher after()
shared after()
.

Move the it_should_behave_like statement to the beginning of the spec and notice how the shared behavior’s before method runs first.

RSpec’s Mocks and Stubs

In Chapter 17, “Testing,” we introduced the concepts of mocks and stubs in association with the Mocha library. RSpec relies heavily on mocking and stubbing.[3] It’s possible to use Mocha together with RSpec, but in our examples we’ll use RSpec’s own mocking and stubbing facilities, which are equally powerful. Actually, they are almost the same—mostly the method names change a little bit.

Mock Objects

To create a mock object, you simply call the mock method anywhere in a spec, and give it a name as an optional parameter. It’s a good idea to give mock objects a name if you will be using more than one of them in your spec. If you use multiple anonymous mocks, you’ll probably have a hard time telling them apart if one fails.

echo = mock('echo')

Remember that you set expectations about what messages are sent to your mock during the course of your spec—mocks will cause a spec to fail if their expectations are not met. Where you would say expects in Mocha to set an expectation on that mock that a message will be passed, in RSpec we say should_receive or should_not_receive.

echo.should_receive(:sound)

Both frameworks have a chained method called with used to set expected parameters, and where in Mocha we would say returns to set the return value, in RSpec we say and_return. It’s close enough that you should be able to switch between frameworks pretty easily, if you need to do so.

echo.should_receive(:sound).with("hey").and_return("hey")

Null Objects

Occasionally you just want an object for testing purposes that accepts any message passed to it—a pattern known as null object. It’s possible to make one using the mock method and the :null_object option.

null_object = mock('null', :null_object => true)

Stub Objects

You can easily create a stub object in RSpec via the stub factory method. You pass stub a name (just like a mock) and default attributes as a hash.

yodeler = stub('yodeler', :yodels? => true)

By the way, there’s no rule that the name parameter of a mock or stub needs to be a string. It’s pretty typical to pass mock or stub a class reference corresponding to the real type of object.

yodeler = stub(Yodeler, :yodels? => true)

The stub factory method is actually just a convenience—what you get back is a Mock object, with predefined method stubs, as you can see from its implementation shown in Listing 18.4:

Example 18.4. File rspec/lib/spec/mocks/spec_methods.rb, Line 22

def stub(name, stubs={})
  object_stub = mock(name)
  stubs.each { |key, value| object_stub.stub!(key).and_return(value) }
  object_stub
end

Partial Mocking and Stubbing

See the stub! method in Listing 18.4? You can use it to install or replace a method on any object, not just mocks—a technique called partial mocking and stubbing.

A partial is RSpec’s way of describing an instance of an existing class that has some mocked or stubbed behavior set on it. Even though RSpec’s authors warn us about the practice in their docs, the ability to do partial mocking and stubbing is actually really crucial to RSpec working well with Rails, particularly when it comes to interactions involving ActiveRecord’s create and find methods.

To see RSpec mocking and stubbing in action, let’s go back and take a look at another Monkeycharger model spec, this time for the Authorizer class. It talks to a payment gateway and specifies how credit card transactions are handled.

You might recall that back in Chapter 17, in the “Rails Mocks?” section, we touched on how external services need to be mocked, so that we don’t end up sending test data to a real service. Listing 18.5 shows you this technique in action, using RSpec mocks and stubs.

Example 18.5. Monkeycharger’s Authorizer Model Spec

describe Authorizer, "processing a non-saved card" do

  before(:each) do
    @card = CreditCard.new(:name => 'Joe Van Dyk',
                           :number => '4111111111111111',
                           :year => 2009, :month => 9,
                           :cvv => '123')
  end

  it "should send authorization request to the gateway" do
    $gateway.should_receive(:authorize)
        .with(599, @card).and_return(successful_authorization)

    Authorizer::authorize!(:credit_card => @card, :amount => '5.99')
  end

  it "should return the transaction id it receives from the gateway" do
    $gateway.should_receive(:authorize)
        .with(599, @card).and_return(successful_authorization)

    Authorizer::authorize!(:credit_card => @card, :amount => '5.99')
        .should == successful_authorization.authorization
   end

  it "authorize! should raise AuthorizationError on failed authorize" do
    $gateway.should_receive(:authorize)
        .with(599, @card).and_return(unsuccessful_authorization)

      lambda {
        Authorizer::authorize!(:credit_card => @card, :amount =>
'5.99')
      }.should raise_error(AuthorizationError,
                           unsuccessful_authorization.message)
  end

  private

   def successful_authorization
      stub(Object, :success? => true, :authorization => '1234')
   end

   def unsuccessful_authorization
      stub(Object, :success? => false, :message => 'reason why it
failed')
   end
end

Running Specs

Specs are executable documents. Each example block is executed inside its own object instance, to make sure that the integrity of each is preserved (with regard to instance variables, etc.).

If I run the credit card specs from Listings 18.1 and 18.2 using the spec command that should have been installed on my system by RSpec, I’ll get output similar to that of Test::Unit—familiar, comfortable, and passing... just not too informative.

$ spec spec/models/credit_card_spec.rb
.........

Finished in 0.330223 seconds

9 examples, 0 failures

Bye-Bye Test::Unit

In case it wasn’t obvious by now, when we’re using RSpec, we don’t have to use Test::Unit any more. They serve similar, mutually exclusive functions: to specify and verify the operation of our application. Both can be used to drive the design of an application in an evolutionary manner according to the precepts of test-driven development (TDD).

There’s a project called test/spec that implements Behavior-Driven Development (BDD) principles on top of Test::Unit, but at the time that I’m writing this, it’s far behind RSpec and doesn’t seem to have much momentum.

RSpec is capable of outputting results of a spec run in many formats. The traditional dots output that looks just like Test::Unit is called progress and, as we saw a moment ago, is the default.

If we add the -fs command-line parameter to spec, we can cause it to output the results of its run in a very different and much more interesting format, the specdoc format. It surpasses anything that Test::Unit is capable of doing on its own “out of the box.”

$ spec -fs spec/models/credit_card_spec.rb

A valid credit card
- should be valid

CreditCard
- should have a valid month
- should have a valid year
- date should not be in the past
- should have two words in the name
- should have two words in the last name if the name is three words
long
- should have one word in the first name if the name is three words
long

We only take Visa and MasterCard
- should not accept amex
- should not accept discover

Finished in 0.301157 seconds

9 examples, 0 failures

Nice, huh? If this is the first time you’re seeing this kind of output, I wouldn’t be surprised if you drifted off in speculation about whether RSpec could help you deal with sadistic PHB-imposed documentation requirements.

We can also do Ruby RDoc-style output:

$ spec -fr spec/models/authorization_spec.rb
# Authorizer a non-saved card
# * the gateway should receive the authorization
# * authorize! should return the transaction id
# * authorize! should throw an exception on a unsuccessful
authorization

Finished in 0.268268 seconds

3 examples, 0 failures

And perhaps the most beautiful output of all, color-coded HTML output, which is what TextMate pops up in a window whenever I run a spec in the editor.

Figure 18.1 shows a successful spec run. If we had failing examples, some of those bars would have been red. Having these sorts of self-documenting abilities is one of the biggest wins you get in choosing RSpec. It actually compels most people to work toward better coverage of their project. I also know from experience that development managers tend to really appreciate RSpec’s output, even incorporating it into their project deliverables.

RSpec HTML-formatted results

Figure 18.1. RSpec HTML-formatted results

Besides the different formatting, there are all sorts of other command-line options available. Just type spec --help to see them all.

Installing RSpec and the RSpec on Rails Plugin

To get started with RSpec on Rails, you need to install the main RSpec library gem. Then install the RSpec on Rails plugin[4] into your project:

sudo gem install rspec

script/plugin install
   svn://rubyforge.org/var/svn/rspec/tags/CURRENT/rspec_on_rails

The project leads actually advise you to install RSpec as a Rails plugin also, so that you can have different versions on a per-project basis.

That does it for our introduction to RSpec. Now we’ll take a look at using RSpec with Ruby on Rails.

The RSpec on Rails Plugin

The RSpec on Rails plugin provides four different contexts for specs, corresponding to the four major kinds of objects you write in Rails. Along with the API support you need to write Rails specs, it also provides code generators and a bundle of Rake tasks.

Generators

Assuming you have the plugin installed already, you should run the rspec generator provided to set up your project for use with RSpec.

$ script/generate rspec
      create  spec
      create  spec/controllers
      create  spec/fixtures
      create  spec/helpers
      create  spec/models
      create  spec/views
      create  spec/spec_helper.rb
      create  spec/spec.opts
      create  previous_failures.txt
      create  script/spec_server
      create  script/spec

A spec directory is created, containing subdirectories for each of the four types of specs. A bunch of additional support files are also created, which we’ll look at in detail later on.

Model Specs

Model specs help you design and verify the domain model of your Rails application, both ActiveRecord and your own classes. RSpec on Rails doesn’t provide too much special functionality for model specs, because there’s not really much needed beyond what’s provided by the base library.

An rspec_model generator is provided, which can be used in place of the default model generator that’s included in Rails. It functions almost the same as its default counterpart, except that it creates a stubbed-out spec in the models directory instead of a stubbed-out test in the test directory. Pass it a class name (capitalized) and pairs of attribute_name:type values. The datetime columns (updated_at/created_at) are automatically added to the migration; no need to specify them.

$ script/generate rspec_model Schedule name:string
      exists  app/models/
      exists  spec/models/
      exists  spec/fixtures/
      create  app/models/schedule.rb
      create  spec/fixtures/schedules.yml
      create  spec/models/schedule_spec.rb
      exists  db/migrate
      create  db/migrate/001_create_schedules.rb

The generated Schedule class is empty and not very interesting. The skeleton spec/models/schedule.rb looks like this:

require File.dirname(__FILE__) + '/../spec_helper'

describe Schedule do
  before(:each) do
    @schedule = Schedule.new
  end

  it "should be valid" do
    @schedule.should be_valid
  end
end

Assume for a moment that the Schedule class has a collection of day objects.

class Schedule < ActiveRecord::Base
  has_many :days
end

Let’s specify that we should be able to get a roll-up total of hours from schedule objects. Instead of fixtures, we’ll mock out the days dependency.

require File.dirname(__FILE__) + '/../spec_helper'

describe Schedule do
  before(:each) do
    @schedule = Schedule.new
  end

  it "should calculate total hours" do
    days_proxy = mock('days')
    days_proxy.should_receive(:sum).with(:hours).and_return(40)
    @schedule.stub!(:days).and_return(days_proxy)
    @schedule.total_hours.should == 40
  end
end

Here we’ve taken advantage of the fact that association proxies in Rails are rich objects. ActiveRecord gives us several methods for running database aggregate functions. We set up an expectation that days_proxy should receive the sum method with one argument—:hours—and return 40.

We can satisfy this specification with a very simple implementation:

class Schedule
  has_many :days

  def total_hours
    days.sum :hours
  end
end

One valid criticism of this approach is that it makes our code harder to refactor. Our spec would fail if we changed the implementation of total_hours to use Enumerable#inject, even though the external behavior doesn’t change. Specifications are not only about describing the visible behavior of objects, but the interactions between an object and its associated objects as well. Mocking the association proxy in this case lets us clearly specify how a Schedule should interact with its Days.

A huge benefit of mocking the days proxy is that we no longer rely on the database[5] in order to write our specifications and implement the total_hours method. Our specs will run very quickly, and we don’t have any messy fixtures to deal with!

Leading mock objects advocates see mock objects as a temporary design tool. You may have noticed that we haven’t defined the Day class yet. So another benefit of using mock objects is that they allow us to specify behavior in true isolation, and during design-time. There’s no need to break our design rhythm by stopping to create the Day class and database table. This may not seem like a big deal for such a simple example, but for more involved specifications it is really helpful to just focus on the design task at hand. After the database and real object models exist, you can go back and replace the mock days_proxy with calls to the real deal. This is a subtle, yet very powerful message about mocks that is usually missed.

Quick Mock ActiveRecord Models

mock_model(model_class, stubs = {})

The mock_model method creates mocks with autogenerated numeric ids and a number of certain common methods stubbed out:

  • idReturns the autogenerated id value

  • to_paramReturns the id value as a string

  • new_record? Returns false

  • errorsReturns a stub errors collection that will report a 0 error count

  • is_a? Returns true if the parameter matches model_class

  • classReturns model_class

You should pass in any additional stubbed method values via the stubs hash argument or set them in a block using the yielded mock instance.

Controller Specs

RSpec gives you the ability to specify your controllers either in isolation from their associated views or together with them, as in regular Rails tests. According to the API docs:

Controller Specs use Spec::Rails::DSL::ControllerBehaviour, which supports running specs for Controllers in two modes, which represent the tension between the more granular testing common in TDD and the more high-level testing built into rails. BDD sits somewhere in between: we want to achieve a balance between specs that are close enough to the code to enable quick fault isolation and far enough away from the code to enable refactoring with minimal changes to the existing specs.

The Controller class is passed to the describe method like this:

describe MessagesController do

An optional second parameter can provide additional information, or you can explicitly use the controller_name method inside a describe block to tell RSpec which controller to use.

describe "Requesting /messages using GET" do
  controller_name :messages
  fixtures :people

I typically group my controller examples by action and HTTP method. Fixtures are available if needed, like any other Rails test or spec. This example requires a logged-in user, so I stub my application controller’s current_person accessor to return a fixture.

before(:each) do
  controller.stub!(:current_person, people(:quentin))

Next, I create a mock Message object using the mock_model method. I want this mock message to be returned whenever Message.find is called during the spec.

@message = mock_model(Message)
  Message.stub!(:find).and_return([@message])
end

Now I can start specifying the behavior of actions (in this case, the index action). The most basic expectation is that the response should be successful, HTTP’s 200 OK response code.

it "should be successful" do
  get :index
  response.should be_success
end

I also want to specify that the find method of Message is called with the proper arguments.

it "should find all the messages" do
  Message.should_receive(:find).with(:all).and_return [@message]
  get :index
end

Additional expectations that should be done for most controller actions include the template to be rendered and variable assignment.

it "should render index.rhtml" do
  get :index
  response.should render_template(:index)
end

it "should assign the found messages for the view" do
  get :index
  assigns[:messages].should include(@message)
end

Previously we saw how to stub out a model’s association proxy. It would be nice not to have to use fixtures in the controller specs. Instead of stubbing the controller’s current_person method to return a fixture, we can have it return a mock person.

@mock_person = mock_model(Person, :name => "Quentin")
controller.stub!(:current_person).and_return @mock_person

Isolation and Integration Modes

By default, RSpec on Rails controller specs run in isolation mode, meaning that view templates are not involved. The benefit of this mode is that you can spec the controller in complete isolation of the view, hence the name. Maybe you can sucker someone else into maintaining the view specs? (The next heading in the chapter is all about spec’ing views.)

Actually, that “sucker” comment is facetious. Having separate view specs is not as difficult as it’s made out to be sometimes. It also provides much better fault isolation, which is a fancy way of saying that you’ll have an easier time figuring out what’s wrong when something fails.

If you prefer to exercise your views in conjunction with your controller logic inside the same controller specs, just as traditional Rails functional tests do, then you can tell RSpec on Rails to run in integration mode using the integrate_views macro. It’s not an all-or-nothing decision—you can specify modes on a per-behavior basis.

describe "Requesting /messages using GET" do
  integrate_views

When you run integrated, the controller specs will be executed once with view rendering turned on.

Specifying Errors

Ordinarily, Rails rescues exceptions that occur during action processing, so that it can respond with a 501 error code and give you that great error page with the stack trace and request variables, and so on. In order to directly specify that an action should raise an error, you have to override the controller’s rescue_action method, by doing something like this:

controller.class.send(:define_method, :rescue_action) { |e| raise e }

If you don’t mind just checking that the response code was an error, you can just use the be_an_error predicate or response_code accessor of the response object:

it "should return an error in the header" do
  response.should be_an_error
end

it "should return a 501" do
  response.response_code.should == 501
end

Specifying Routes

One of Rails’ central components is routing. The routing mechanism is the way Rails takes an incoming request URL and maps it to the correct controller and action. Given its importance, it is a good idea to specify the routes in your application. You can do this with the route_for method in a controller spec.

describe MessagesController, "routing" do
  it "should map { :controller => 'messages', :action => 'index' } to
  /messages" do
    route_for(:controller => "messages", :action => "index").should ==
  "/messages"
  end

  it "should map { :controller => 'messages', :action => 'edit',
  :id => 1 }
 to /messages/1;edit" do
    route_for(:controller => "messages", :action => "edit",
  :id => 1).should == "/messages/1;edit"
  end
end

View Specs

Controller specs let us integrate the view to make sure there are no errors with the view, but we can do one better by specifying the views themselves. RSpec will let us write a specification for a view, completely isolated from the underlying controller. We can specify that certain tags exist and that the right data is outputted.

Let’s say we want to write a page that displays a private message sent between members of an internet forum. RSpec creates the spec/views/messages directory when we use the rspec_controller generator. The first thing we would do is create a file in that directory for the show view, naming it show_rhtml_spec.rb. Next we would set up the information to be displayed on the page.

describe "messages/show.rhtml" do
  before(:each) do
    @message = mock_model(Message, :subject => "RSpec rocks!")
    sender = mock_model(Person, :name => "Obie Fernandez")
    @message.stub!(:sender).and_return(sender)
    recipient = mock_model(Person, :name => "Pat Maddox")
    @message.stub!(:recipient).and_return(recipient)

If you want to be a little more concise at the cost of one really long line of code that you’ll have to break up into multiple lines, you can inline the creation of the mocks like this:

describe "messages/show.rhtml" do
  before(:each) do
    @message = mock_model(Message,
      :subject => "RSpec rocks!",
      :sender => mock_model(Person, :name => "Obie Fernandez"),
      :recipient => mock_model(Person, :name => "Pat Maddox"))

Either way, this is standard mock usage similar to what we’ve seen before. Again, mocking the objects used in the view allows us to completely isolate the specification.

Assigning Instance Variables

We now need to assign the message to the view. The rspec_on_rails plugin gives us a familiar-looking assigns method, which you can treat as a hash.

  assigns[:message] = @message
end

Fantastic! Now we are ready to begin specifying the view page. We’d like to specify that the message subject is displayed, wrapped in an <h1> tag. The have_tag expectation takes two arguments—the tag selector and the content within the tag. It wraps the assert_select functionality included with Rails testing, which we covered extensively in Chapter 17.

it "should display the message subject" do
  render "messages/show"
  response.should have_tag('h1', 'RSpec rocks!')
end

HTML tags often have an ID associated with them. We would like our page to create a <div> with the ID message_info for displaying the sender and recipient’s names. We can pass the ID to have_tag as well.

it "should display a div with id message_info" do
  render "messages/show"
  response.should have_tag('div#message_info')
end

What if we want to specify that the sender and recipient’s names should appear in <h3> tags within the div?

it "should display sender and recipient names in div#message_info" do
  render "messages/show"
  response.should have_tag('div#message_info') do
    with_tag('h3#sender', 'Sender: Obie Fernandez')
    with_tag('h3#recipient', 'Recipient: Pat Maddox')
  end
end

Stubbing Helper Methods

Note that the view specs do not mix in helper methods automatically, in order to preserve isolation. If your view template code relies on helper methods, you need to mock or stub them out on the provided template object.

The decision to mock versus stub those helper methods should depend on whether they’re an active player in the behavior you want to specify, as in the following example:

it "should truncate subject lines" do
  template.should_receive(:truncate).exactly(2).times
  render "messages/index"
end

If you forget to mock or stub helper method calls, your spec will fail with a NoMethodError.

Helper Specs

Speaking of helpers, it’s really easy to write specs for your custom helper modules. Just pass describe to your helper module and it will be mixed into the spec class so that its methods are available to your example code.

describe ProfileHelper do
  it "profile_photo should return nil if user's photos is empty" do
    user = mock_model(User, :photos => [])
    profile_photo(user).should == nil
  end
end

It’s worth noting that in contrast to view specs, all of the framework-provided ActionView::Helper modules are mixed into helper specs, so that they’re available to your helper code. All dynamically generated routes helper methods are added too.

Scaffolding

Rails comes with the scaffold_resource generator to easily create RESTful controllers and the underlying models. The rspec_on_rails plugin provides the rspec_scaffold generator, which does the same thing using RSpec instead of Test::Unit.

Play around with rspec_scaffold when you have some free time—the generated specs are another source of very good example spec code for all three MVC layers. It generates specs that cover the Rails-generated code 100%, making it a very good learning tool.

RSpec Tools

There are several open-source projects that enhance RSpec’s functionality and your productivity. (None of these tools are unique to RSpec. In fact they all were originally written for Test::Unit.)

Autotest

The Autotest project is part of the ZenTest suite[6] created by Ryan Davis and Eric Hodel. As the name implies, it automatically runs your test suite for you. Each time you save a file in your project, Autotest will run any spec files that may be affected by the change. This is an excellent tool for getting in a solid red-green-refactor rhythm because you won’t have to switch windows to manually run the tests. In fact you don’t even need to run any command! Just cd to your project’s directory and type autospec to kick things off.

RCov

RCov is a code coverage tool for Ruby.[7] You can run it on a spec file to see how much of your production code is covered. It provides HTML output to easily tell what code is covered by specs and what isn’t. You can RCov individually on a spec file, or the rspec_on_rails plugin provides the spec:rcov task for running all of your specs under RCov. The results are outputted into a directory named coverage and contain a set of HTML files that you can browse by opening index.html (as shown in Figure 18.2):

A sample RCov coverage reportHeckle

Figure 18.2. A sample RCov coverage reportHeckle

Heckle is part of the Seattle Ruby Brigade’s awesome collection of projects,[8] and is another code coverage tool. Instead of simply checking the scope of your tests, Heckle helps you measure the effectiveness of your specs. It actually goes into your code and scrambles things like variable values and if statements. If none of your specs break, you’re missing a spec somewhere.

The current versions of RSpec have Heckle support built-in. Just experiment with the --heckle option and see what happens.

Conclusion

You’ve gotten a taste of the different testing experience that RSpec delivers. At first it may seem like the same thing as Test::Unit with some words substituted and shifted around. One of the key points of TDD is that it’s about design rather than testing. This is a lesson that every good TDDer learns through lots of experience. RSpec uses a different vocabulary and style to emphasize that point. It comes with the lesson baked in so that you can attain the greatest benefits of TDD right away.

References

1.

Well, other than to write Chapter 17, which has full coverage of Test::Unit.

2.

You can grab a copy of the Monkeycharger project at http://monkeycharger.googlecode.com/.

3.

Confused about the difference between mocks and stubs? Read Martin Fowler’s explanation at http://www.martinfowler.com/articles/mocksArentStubs.html.

4.

If you have firewall trouble with that plugin because of the svn:// address, please follow the instructions at http://rspec.rubyforge.org/documentation/rails/install.html.

5.

Actually that’s not quite true. ActiveRecord still connects to the database to get the column information for Schedule. However, you could actually stub that information out as well to completely remove dependence on the database.

6.

http://rubyforge.org/projects/zentest/.

7.

http://rubyforge.org/projects/rcov.

8.

http://rubyforge.org/projects/seattlerb/.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.46.92