Controller Specs

Overview

Merb was designed so that a controller called by a test executes the same way as from a user’s request. Controllers are instantiated with the entire request as an argument. This means calling an action can be handled by faking the request object, which is exactly what the controller helpers do. This also means that faked requests execute actions exactly the same way as real requests.

Keep In Mind

Controller specs work best when the interaction with models have been completely stubbed out. Checkout the RSpec documentation for more info on how to stub & mock for your models.

It is also a good idea to stub the render and display methods for your controller. These methods invoke the view, which should be left to the view specs.

Examples

Lets say we have an Articles controller with an index we want to spec:


class Articles < Application
  def index
    @articles = Article.all(:order => 'created_at desc')
    display @articles
  end
end

We could spec this index action to make sure it always fetches the articles from the database in descending order by date:


describe Articles do

  describe "#index" do
    it "fetches all articles in descending order of date created" do
      Article.should_receive(:all).with(:order => 'created_at desc')

      dispatch_to(Articles, :index) do |controller|
        controller.stub!(:display)
      end
    end
  end
end

This will make sure the index action will always display the articles in descending order without actually querying the database or rendering the view, meaning you don’t have to worry about populating the database from fixtures or rendering errors. It is not always the case so you can use fixtures and do not stub display so you can operate on response body to do some assertions.

Lets add a show action with an id parameter:


class Articles < Application

  def index
    @articles = Article.all(:order => 'created_at desc')
    display @articles
  end

  def show(id)
    begin
      @article = Article.with_slug(id)
      display @article
    rescue YourOrm::RecordNotFoundOrSomething
      raise NotFound
    end    
  end
end

And the spec for an action with a parameter (if you really feel you have to stub display):


describe Articles do

  describe "#show" do

    before(:each) do
      @article = mock(:article)
    end

    it "should display the article matching the slug id" do
      Article.stub(:with_slug).with("slug").and_return @article

      dispatch_to(Articles, :show, :id => "slug") do |controller|
        controller.should_receive(:display).with(@article)
      end
    end
  end
end

Notice that the request path and method are not specified anywhere. Merb dispatch_to helper allows you to invoke an action without specifying the request method, keeping your routes removed from your controller tests. If you want to spec your routes (and you always do), Merb provides helpers & matchers specifically for testing your routes. See the [Routing Specs] for more. If your action’s behavior depends on the request method, the get, post, put, delete will allow you to fake the request method and NOT bypassing routing.

Taming Spec Complexity

Sometimes your specs can grow quite complex. It’s often handy to abstract away some complexity. Here are some tips for handling complexity with abstraction.

Handling Requests

Did you notice in the above examples the frequent usage of dispatch_to? It’s sometimes best to wrap up the test request to your controller so that each example in your spec is making the same sort of request. For example:


describe "requesting /pages with GET" do
  before(:each) do
    Page.stub!(:all).and_return(pages)
  end

  def do_get
    dispatch_to(Pages, :index) do |controller|
      controller.stub!(:display)
    end
  end

  it "should be successful" do
    do_get.should be_successful
  end

  it "should load all page records" do
    Page.should_receive(:all).and_return(pages)
    do_get.assigns(:pages).should == pages # pages is defined elsewhere
  end

  it "should display all pages" do
    dispatch_to(Pages, :index) do |controller|
      controller.should_receive(:display).with(pages) # pages is defined elsewhere
    end
  end
end

The method do_get neatly summarizes the most common request we’re making in our spec, handles any stubs, and has a wonderfully descriptive name to describe what’s happening.

Notice in the last example in the above spec that we don’t have to use do_get. In this last example we hand write our usage of dispatch_to to ensure that a particular method is being called.

Edit Page | History | Version: