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.