用于为嵌套资源创建 Controller 操作的 RSpec 测试

标签 rspec controller nested

我有一个 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

不幸的是,代码还没有工作。

最佳答案

对于嵌套资源,您需要build设置数据和帖子,以便在发布子评论时识别父文章。

一种方法是正确设置 Factory Girl 关联,然后确保在创建子属性时设置父元素。它看起来像这样:

在评论工厂中:

FactoryGirl.define do
  Factory :comment do
    comment "My comment"
    article
  end
end

通过调用文章,并确保有一个名为 :article 的有效工厂。那么 FactoryGirl 将在创建评论时创建一篇文章。为了使测试顺利进行,我们实际上应该具体说明哪个 article用于 comment已创建,因此现在工厂已就位,我们在规范中使用以下内容。
@comment_attributes = FactoryGirl.attributes_for(:comment, :article_id => @article)

这将构建自动附加到@article 的评论属性。最后一部分是构建帖子,确保我们包括父级和子级。

当发布嵌套资源时,它需要父资源和子资源的参数。在 rspec 中,我们可以在帖子中提供如下:
post :create, :article_id => @article, :comment => @comment_attributes

这应该正确连接所有部分。

关于用于为嵌套资源创建 Controller 操作的 RSpec 测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9055866/

相关文章:

ruby-on-rails-3 - Rails 3 升级后 rake db :test:prepare not working?

javascript - Angular 跨度条件悬停在文本上

ruby-on-rails - ruby rails : is there a standard helper for minimalistic implementing of create and update actions?

java - Controller 不支持 Spring

python - 嵌套字典的下键和合并数据(如果下键已经存在)

Mercurial 嵌套 repo 问题

ruby-on-rails - RSpec:如何创建辅助 stub 方法?

ruby - 我可以给 RSpec 匹配器取别名吗?

ruby-on-rails - 不推荐在模板名称中传递模板处理程序是什么意思。意思是?

java - 我无法让我的嵌套 for 循环参与