RSpec 测试嵌套资源控制器的创建操作
我有一个 Rails 应用程序(Rails 3.0.10),用户可以在其中拥有许多文章,并且用户可以在其中对文章发表评论。评论在文章展示页面进行。
现在我想测试 CommentsController 的创建操作,但是,我在使用正确的参数调用 post 方法时遇到问题。
这是 CommentsController 的代码:
class CommentsController < ApplicationController
# create a comment and bind it to an article and a user
def create
@article = Article.find(params[:article_id])
@user = User.find(@article.user_id)
@comment = @article.comments.build(params[:comment])
@comment.user_id = current_user.id
commenters = []
@article.comments.each {
|comment|
commenters << User.find(comment.user_id)
}
commenters.uniq!
respond_to do |format|
if @comment.save
#Notify user who offers article on new comment, else notify the commenters
if @article.user_id != @comment.user_id
UserMailer.new_article_comment_email(@user, @comment).deliver
else
commenters.each {
|commenter|
UserMailer.new_article_comment_email(commenter, @comment).deliver
}
end
format.html {
redirect_to(@article)
flash[:notice] = t(:comment_create_success)
}
else
format.html {
redirect_to(@article)
flash[:error] = t(:comment_create_error)
}
end
end
end
end
用于测试此操作的 RSpec 代码(到目前为止的一些实验)如下:
require 'spec_helper'
require 'ruby-debug'
describe CommentsController do
render_views
describe "POST 'create'" do
before(:each) do
@user = FactoryGirl.create(:user)
@article = FactoryGirl.build(:article)
@article.user_id = @user.id
@article.save
@article_attributes = FactoryGirl.attributes_for(:article)
@comment_attributes = FactoryGirl.attributes_for(:comment)
end
it "should create a new comment" do
expect {
post :create, :comment => @comment_attributes
}.to change(Comment, :count).by(1)
end
it "should create a new comment, redirect to the article show page of this comment and notify the user on successful saving of the comment" do
post :create, :comment => @comment_attributes, :article_id => @article.id.to_s, :user_id => @user.id.to_s
flash[:notice].should_not be_nil
response.should redirect_to(article_path(@article))
end
end
end
两个测试都失败了,但是,由于我无法修复的不同原因:
Failures:
1) CommentsController POST 'create' should create a new comment
Failure/Error: post :create, :comment => @comment_attributes
ActionController::RoutingError:
No route matches {:comment=>{:body=>"This is the body text of a comment"}, :controller=>"comments", :action=>"create"}
# ./spec/controllers/comments_controller_spec.rb:22:in `block (4 levels) in <top (required)>'
# ./spec/controllers/comments_controller_spec.rb:21:in `block (3 levels) in <top (required)>'
2) CommentsController POST 'create' should create a new comment, redirect to the article show page of this comment and notify the user on successful saving of the comment
Failure/Error: post :create, :comment => @comment_attributes, :article_id => @article.id.to_s, :user_id => @user.id.to_s
RuntimeError:
Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
# ./app/controllers/comments_controller.rb:8:in `create'
# ./spec/controllers/comments_controller_spec.rb:27:in `block (3 levels) in <top (required)>'
如果有人可以帮助我,我会很棒出去。提前致谢!
更新:这是我正在使用的routes.rb:
Cinderella::Application.routes.draw do
# The priority is based upon order of creation:
# first created -> highest priority.
# Sample of regular route:
# match 'products/:id' => 'catalog#view'
# Keep in mind you can assign values other than :controller and :action
# Sample of named route:
# match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
# This route can be invoked with purchase_url(:id => product.id)
match '/signup', :to => 'users#new'
match '/signin', :to => 'sessions#new'
match '/signout', :to => 'sessions#destroy'
match '/home', :to => 'pages#home'
match '/about', :to => 'pages#about'
match '/faq', :to => 'pages#faq'
match '/howitworks_sellers', :to => "pages#howitworks_sellers"
match '/howitworks_buyers', :to => "pages#howitworks_buyers"
match '/contact', :to => 'pages#contact'
match '/articles/:id/ratings', :to => 'ratings#destroy'
# Sample resource route (maps HTTP verbs to controller actions automatically):
# resources :products
resources :articles do
resources :comments, :only => [:create, :destroy]
end
resources :ratings
resources :ratings do
collection do
post 'destroy'
end
end
resources :users do
resources :articles
end
resources :sessions, :only => [:new, :create, :destroy]
# Sample resource route with options:
# resources :products do
# member do
# get 'short'
# post 'toggle'
# end
#
# collection do
# get 'sold'
# end
# end
# Sample resource route with sub-resources:
# resources :products do
# resources :comments, :sales
# resource :seller
# end
# Sample resource route with more complex sub-resources
# resources :products do
# resources :comments
# resources :sales do
# get 'recent', :on => :collection
# end
# end
# Sample resource route within a namespace:
# namespace :admin do
# # Directs /admin/products/* to Admin::ProductsController
# # (app/controllers/admin/products_controller.rb)
# resources :products
# end
# You can have the root of your site routed with "root"
# just remember to delete public/index.html.
root :to => "pages#home"
# See how all your routes lay out with "rake routes"
# This is a legacy wild controller route that's not recommended for RESTful applications.
# Note: This route will make all actions in every controller accessible via GET requests.
# match ':controller(/:action(/:id(.:format)))'
end
#== Route Map
# Generated on 14 Dec 2011 14:24
#
# signin /signin(.:format) {:controller=>"sessions", :action=>"new"}
# signout /signout(.:format) {:controller=>"sessions", :action=>"destroy"}
# home /home(.:format) {:controller=>"pages", :action=>"home"}
# about /about(.:format) {:controller=>"pages", :action=>"about"}
# faq /faq(.:format) {:controller=>"pages", :action=>"faq"}
# articles GET /articles(.:format) {:action=>"index", :controller=>"articles"}
# POST /articles(.:format) {:action=>"create", :controller=>"articles"}
# new_article GET /articles/new(.:format) {:action=>"new", :controller=>"articles"}
# edit_article GET /articles/:id/edit(.:format) {:action=>"edit", :controller=>"articles"}
# article GET /articles/:id(.:format) {:action=>"show", :controller=>"articles"}
# PUT /articles/:id(.:format) {:action=>"update", :controller=>"articles"}
# DELETE /articles/:id(.:format) {:action=>"destroy", :controller=>"articles"}
# user_articles GET /users/:user_id/articles(.:format) {:action=>"index", :controller=>"articles"}
# POST /users/:user_id/articles(.:format) {:action=>"create", :controller=>"articles"}
# new_user_article GET /users/:user_id/articles/new(.:format) {:action=>"new", :controller=>"articles"}
# edit_user_article GET /users/:user_id/articles/:id/edit(.:format) {:action=>"edit", :controller=>"articles"}
# user_article GET /users/:user_id/articles/:id(.:format) {:action=>"show", :controller=>"articles"}
# PUT /users/:user_id/articles/:id(.:format) {:action=>"update", :controller=>"articles"}
# DELETE /users/:user_id/articles/:id(.:format) {:action=>"destroy", :controller=>"articles"}
# users GET /users(.:format) {:action=>"index", :controller=>"users"}
# POST /users(.:format) {:action=>"create", :controller=>"users"}
# new_user GET /users/new(.:format) {:action=>"new", :controller=>"users"}
# edit_user GET /users/:id/edit(.:format) {:action=>"edit", :controller=>"users"}
# user GET /users/:id(.:format) {:action=>"show", :controller=>"users"}
# PUT /users/:id(.:format) {:action=>"update", :controller=>"users"}
# DELETE /users/:id(.:format) {:action=>"destroy", :controller=>"users"}
# sessions POST /sessions(.:format) {:action=>"create", :controller=>"sessions"}
# new_session GET /sessions/new(.:format) {:action=>"new", :controller=>"sessions"}
# session DELETE /sessions/:id(.:format) {:action=>"destroy", :controller=>"sessions"}
# root /(.:format) {:controller=>"pages", :action=>"home"}
更新:这是我根据 nmotts 建议所做的修改:
require 'spec_helper'
require 'ruby-debug'
describe CommentsController do
render_views
describe "POST 'create'" do
before(:each) do
@user = FactoryGirl.create(:user)
@article = FactoryGirl.build(:article)
@article.user_id = @user.id
@article.save
@comment_attributes = FactoryGirl.attributes_for(:comment, :article_id => @article)
end
it "should create a new comment" do
post :create, :article_id => @article.id.to_s, :comment => @comment_attributes
end
end
end
以及FactoryGirl 注释定义:
factory :comment do
body "This is the body text of a comment"
article
end
不幸的是,代码尚未运行。
I have a Rails application (Rails 3.0.10) where users can have many articles, and where the users can leave comments on the articles. Comments are made on the article show page.
Now I want to test the create action of the CommentsController, however, I have problems of invoking the post method with the right parameters.
Here's the code of the CommentsController:
class CommentsController < ApplicationController
# create a comment and bind it to an article and a user
def create
@article = Article.find(params[:article_id])
@user = User.find(@article.user_id)
@comment = @article.comments.build(params[:comment])
@comment.user_id = current_user.id
commenters = []
@article.comments.each {
|comment|
commenters << User.find(comment.user_id)
}
commenters.uniq!
respond_to do |format|
if @comment.save
#Notify user who offers article on new comment, else notify the commenters
if @article.user_id != @comment.user_id
UserMailer.new_article_comment_email(@user, @comment).deliver
else
commenters.each {
|commenter|
UserMailer.new_article_comment_email(commenter, @comment).deliver
}
end
format.html {
redirect_to(@article)
flash[:notice] = t(:comment_create_success)
}
else
format.html {
redirect_to(@article)
flash[:error] = t(:comment_create_error)
}
end
end
end
end
The RSpec code for testing this action (some experiments so far) is the following:
require 'spec_helper'
require 'ruby-debug'
describe CommentsController do
render_views
describe "POST 'create'" do
before(:each) do
@user = FactoryGirl.create(:user)
@article = FactoryGirl.build(:article)
@article.user_id = @user.id
@article.save
@article_attributes = FactoryGirl.attributes_for(:article)
@comment_attributes = FactoryGirl.attributes_for(:comment)
end
it "should create a new comment" do
expect {
post :create, :comment => @comment_attributes
}.to change(Comment, :count).by(1)
end
it "should create a new comment, redirect to the article show page of this comment and notify the user on successful saving of the comment" do
post :create, :comment => @comment_attributes, :article_id => @article.id.to_s, :user_id => @user.id.to_s
flash[:notice].should_not be_nil
response.should redirect_to(article_path(@article))
end
end
end
Both tests fail, however, due to different reasons that I am unable to fix:
Failures:
1) CommentsController POST 'create' should create a new comment
Failure/Error: post :create, :comment => @comment_attributes
ActionController::RoutingError:
No route matches {:comment=>{:body=>"This is the body text of a comment"}, :controller=>"comments", :action=>"create"}
# ./spec/controllers/comments_controller_spec.rb:22:in `block (4 levels) in <top (required)>'
# ./spec/controllers/comments_controller_spec.rb:21:in `block (3 levels) in <top (required)>'
2) CommentsController POST 'create' should create a new comment, redirect to the article show page of this comment and notify the user on successful saving of the comment
Failure/Error: post :create, :comment => @comment_attributes, :article_id => @article.id.to_s, :user_id => @user.id.to_s
RuntimeError:
Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
# ./app/controllers/comments_controller.rb:8:in `create'
# ./spec/controllers/comments_controller_spec.rb:27:in `block (3 levels) in <top (required)>'
I would be great if someone could help me out. Thanks in advance!
Update: Here's the routes.rb I am using:
Cinderella::Application.routes.draw do
# The priority is based upon order of creation:
# first created -> highest priority.
# Sample of regular route:
# match 'products/:id' => 'catalog#view'
# Keep in mind you can assign values other than :controller and :action
# Sample of named route:
# match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
# This route can be invoked with purchase_url(:id => product.id)
match '/signup', :to => 'users#new'
match '/signin', :to => 'sessions#new'
match '/signout', :to => 'sessions#destroy'
match '/home', :to => 'pages#home'
match '/about', :to => 'pages#about'
match '/faq', :to => 'pages#faq'
match '/howitworks_sellers', :to => "pages#howitworks_sellers"
match '/howitworks_buyers', :to => "pages#howitworks_buyers"
match '/contact', :to => 'pages#contact'
match '/articles/:id/ratings', :to => 'ratings#destroy'
# Sample resource route (maps HTTP verbs to controller actions automatically):
# resources :products
resources :articles do
resources :comments, :only => [:create, :destroy]
end
resources :ratings
resources :ratings do
collection do
post 'destroy'
end
end
resources :users do
resources :articles
end
resources :sessions, :only => [:new, :create, :destroy]
# Sample resource route with options:
# resources :products do
# member do
# get 'short'
# post 'toggle'
# end
#
# collection do
# get 'sold'
# end
# end
# Sample resource route with sub-resources:
# resources :products do
# resources :comments, :sales
# resource :seller
# end
# Sample resource route with more complex sub-resources
# resources :products do
# resources :comments
# resources :sales do
# get 'recent', :on => :collection
# end
# end
# Sample resource route within a namespace:
# namespace :admin do
# # Directs /admin/products/* to Admin::ProductsController
# # (app/controllers/admin/products_controller.rb)
# resources :products
# end
# You can have the root of your site routed with "root"
# just remember to delete public/index.html.
root :to => "pages#home"
# See how all your routes lay out with "rake routes"
# This is a legacy wild controller route that's not recommended for RESTful applications.
# Note: This route will make all actions in every controller accessible via GET requests.
# match ':controller(/:action(/:id(.:format)))'
end
#== Route Map
# Generated on 14 Dec 2011 14:24
#
# signin /signin(.:format) {:controller=>"sessions", :action=>"new"}
# signout /signout(.:format) {:controller=>"sessions", :action=>"destroy"}
# home /home(.:format) {:controller=>"pages", :action=>"home"}
# about /about(.:format) {:controller=>"pages", :action=>"about"}
# faq /faq(.:format) {:controller=>"pages", :action=>"faq"}
# articles GET /articles(.:format) {:action=>"index", :controller=>"articles"}
# POST /articles(.:format) {:action=>"create", :controller=>"articles"}
# new_article GET /articles/new(.:format) {:action=>"new", :controller=>"articles"}
# edit_article GET /articles/:id/edit(.:format) {:action=>"edit", :controller=>"articles"}
# article GET /articles/:id(.:format) {:action=>"show", :controller=>"articles"}
# PUT /articles/:id(.:format) {:action=>"update", :controller=>"articles"}
# DELETE /articles/:id(.:format) {:action=>"destroy", :controller=>"articles"}
# user_articles GET /users/:user_id/articles(.:format) {:action=>"index", :controller=>"articles"}
# POST /users/:user_id/articles(.:format) {:action=>"create", :controller=>"articles"}
# new_user_article GET /users/:user_id/articles/new(.:format) {:action=>"new", :controller=>"articles"}
# edit_user_article GET /users/:user_id/articles/:id/edit(.:format) {:action=>"edit", :controller=>"articles"}
# user_article GET /users/:user_id/articles/:id(.:format) {:action=>"show", :controller=>"articles"}
# PUT /users/:user_id/articles/:id(.:format) {:action=>"update", :controller=>"articles"}
# DELETE /users/:user_id/articles/:id(.:format) {:action=>"destroy", :controller=>"articles"}
# users GET /users(.:format) {:action=>"index", :controller=>"users"}
# POST /users(.:format) {:action=>"create", :controller=>"users"}
# new_user GET /users/new(.:format) {:action=>"new", :controller=>"users"}
# edit_user GET /users/:id/edit(.:format) {:action=>"edit", :controller=>"users"}
# user GET /users/:id(.:format) {:action=>"show", :controller=>"users"}
# PUT /users/:id(.:format) {:action=>"update", :controller=>"users"}
# DELETE /users/:id(.:format) {:action=>"destroy", :controller=>"users"}
# sessions POST /sessions(.:format) {:action=>"create", :controller=>"sessions"}
# new_session GET /sessions/new(.:format) {:action=>"new", :controller=>"sessions"}
# session DELETE /sessions/:id(.:format) {:action=>"destroy", :controller=>"sessions"}
# root /(.:format) {:controller=>"pages", :action=>"home"}
Update: Here's the modification I did according to nmotts suggestions:
require 'spec_helper'
require 'ruby-debug'
describe CommentsController do
render_views
describe "POST 'create'" do
before(:each) do
@user = FactoryGirl.create(:user)
@article = FactoryGirl.build(:article)
@article.user_id = @user.id
@article.save
@comment_attributes = FactoryGirl.attributes_for(:comment, :article_id => @article)
end
it "should create a new comment" do
post :create, :article_id => @article.id.to_s, :comment => @comment_attributes
end
end
end
And the FactoryGirl definition for comment:
factory :comment do
body "This is the body text of a comment"
article
end
Unfortunately, the code is not yet working.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
对于嵌套资源,您需要构建设置数据和帖子,以便在发布子评论时识别父文章。
一种方法是正确设置 Factory Girl 关联,然后确保在创建子属性时设置父元素。它看起来像这样:
在评论工厂中:
通过调用article,并确保有一个名为
:article
的有效工厂,那么FactoryGirl将在创建评论时创建一篇文章。为了使测试顺利进行,我们实际上应该在创建comment
时具体说明使用哪个article
,因此现在工厂已经就位,我们在规格这将构建自动附加到@article 的评论属性。最后一步是构建帖子,确保我们包括父母和孩子。
当发布嵌套资源时,它需要父资源和子资源的参数。在 rspec 中,我们可以在帖子中提供如下内容:
这应该正确链接所有部分。
For a nested resource you need to construct the setup data and the post in such a way as to identify the parent article when posting the child comment.
One approach is to setup Factory Girl associations correctly and then ensure the parent element is set when creating the child attributes. It would look something like this:
In the comment factory:
By calling article, and making sure that there is a valid factory called
:article
then FactoryGirl will create an article when a comment is created. To make the tests flow well we should actually be specific about whicharticle
is used when thecomment
is created, so now that the Factory is in place we use the following in the spec.This will build comment attributes which are automatically attached to @article. The final piece is then to construct the post, making sure that we include the parent and the child.
When a nested resource is posted it expects params for both the parent resource and the child. In rspec we can provide this in the post as follows:
This should link up all the pieces correctly.