I’ve been using rspec quite a bit lately, but ran into a small roadblock when I needed to test nested resources in Rails. After a quick Google search and a few IMs later, here’s the solution I came up with. (These tests cover the index action of the nested resource)
This contrived application will use a classic example, posts have many comments, and a comment belongs to a post.
Create a test rails app and <em>cd</em> into the directory of the newly created app:
$ rails nestedresourceapp
$ cd nestedresourceapp
Next, install the rspec and rspec-rails gems (http://wiki.github.com/dchelimsky/rspec/rails):
$ ruby script/plugin install git://github.com/dchelimsky/rspec.git -r 'refs/tags/1.2.7'
$ ruby script/plugin install git://github.com/dchelimsky/rspec-rails.git -r 'refs/tags/1.2.7.1'
$ ruby script/generate rspec
Generate the Post and Comment rspec scaffolds:
$ script/generate rspec_scaffold Post
$ script/generate rspec_scaffold Comment
Add the associations to the models:
# app/models/post.rb
class Post < ActiveRecord::Base
has_many :post
end
# app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :post
end
Modify the routes file to include the nested resources:
# config/routes.rb
map.resources :posts do |post|
post.resources :comments
end
Update the migrations to include a few basic fields:
# db/migrate/20090716010730_create_posts.rb
class CreatePosts < ActiveRecord::Migration
def self.up
create_table :posts do |t|
t.string "title"
t.text "body"
t.timestamps
end
end
def self.down
drop_table :posts
end
end
# db/migrate/20090716010737_create_comments.rb
class CreateComments < ActiveRecord::Migration
def self.up
create_table :comments do |t|
t.text "comment"
t.integer "post_id"
t.timestamps
end
end
def self.down
drop_table :comments
end
end
Add the following to the spec helper file:
# spec/spec_helper.rb
module Spec
module Mocks
module Methods
def stub_association!(association_name, methods_to_be_stubbed = {})
mock_association = Spec::Mocks::Mock.new(association_name.to_s)
methods_to_be_stubbed.each do |method, return_value|
mock_association.stub!(method).and_return(return_value)
end
self.stub!(association_name).and_return(mock_association)
end
end
end
end
And finally, the tests themselves:
# spec/controllers/comments_controller_spec.rb
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe CommentsController, "handling GET /posts/1/comments" do
before do
@post = mock_model(Post, :to_param => "1")
@post.stub_association!(:comments, :find => mock_model(Comment))
Post.stub!(:find).and_return(@post)
end
def do_get
get :index, :post_id => 1
end
it "should be successful" do
do_get
response.should be_success
end
it "should render index template" do
do_get
response.should render_template('index')
end
end
Running the tests should give you an out similar to mine:
scriptrunner@scriptrunner-laptop:~/Projects/nestedresources$ rake spec
(in /home/scriptrunner/Projects/nestedresources)
......
Finished in 0.080328 seconds
6 examples, 0 failures
Comments [0]