2016-05-16 33 views
1

我想測試控制器規範中的操作,但出於某種原因,我得到了無路徑匹配錯誤。我應該做些什麼來使路線起作用?rails + rspec控制器規範與多態關聯

ActionController::UrlGenerationError: 
    No route matches {:action=>"create", :comment=>{:body=>"Consectetur quo accusamus ea.", 
    :commentable=>"4"}, :controller=>"comments", :post_id=>"4"} 

模型

class Comment < ActiveRecord::Base 
    belongs_to :commentable, polymorphic: true, touch: true 

class Post < ActiveRecord::Base 
    has_many :comments, as: :commentable, dependent: :destroy 

路由

resources :posts do 
    resources :comments, only: [:create, :update, :destroy], module: :posts 
end 

controller_spec

describe "POST create" do 
    let!(:user) { create(:user) } 
    let!(:profile) { create(:profile, user: @user) } 
    let!(:commentable) { create(:post, user: @user) } 

    context "with valid attributes" do 
    subject(:create_action) { xhr :post, :create, post_id: commentable, comment: attributes_for(:comment, commentable: commentable, user: @user) } 

    it "saves the new task in the db" do 
     expect{ create_action }.to change{ Comment.count }.by(1) 
    end 
    ... 

EDIT

從上面的controller_spec可以spec/controllers/comments_controller_spec.rb

控制器找到/ comments_controller.rb

class CommentsController < ApplicationController 
    before_action :authenticate_user! 

    def create 
    @comment = @commentable.comments.new(comment_params) 
    authorize @comment 
    @comment.user = current_user 
    if @comment.save 
     @comment.send_comment_creation_notification(@commentable) 
     respond_to :js 
    end 
    end 

控制器/帖/ comments_controller.rb

class Posts::CommentsController < CommentsController 
    before_action :set_commentable 

    private 

    def set_commentable 
     @commentable = Post.find(params[:post_id]) 
    end 

回答

2

使用module: :posts意願路線Posts::CommentsController#create

如果這不是你想要除去的模塊選項。

否則,您需要確保您的控制器和規範都具有正確的類名稱。

class Posts::CommentsController 
    def create 

    end 
end 

RSpec.describe Posts::CommentsController do 
    # ... 
end 

另請注意,如果爲嵌套資源的「單獨操作」往往沒有意義。

相反,你可能要聲明,像這樣的路線:

resources :comments, only: [:update, :destroy] # show, edit ... 

resources :posts do 
    resources :comments, only: [:create], module: :posts # new, index 
end 

它給你:

class CommentsController < ApplicationController 

    before_action :set_posts 

    # DELETE /comments/:id 
    def destroy 
    # ... 
    end 

    # PUT|PATCH /comments/:id 
    def update 
    end 
end 

class Posts::CommentsController < ApplicationController 
    # POST /posts/:post_id/comments 
    def create 
    # ... 
    end 
end 

對於爲什麼一個更深的解釋請參見Avoid Deeply Nested Routes in Rails


設置了控制器在這種情況下使用繼承是一個好主意 - 但你無法通過,因爲RSpec的控制器規範父CommentsController類測試create方法將總是看described_class試圖解決的時候路線。

相反,你可能需要使用共享的例子:

# /spec/support/shared_examples/comments.rb 
RSpec.shared_examples "nested comments controller" do |parameter| 
    describe "POST create" do 
    let!(:user) { create(:user) } 

    context "with valid attributes" do 
     subject(:create_action) { xhr :post, :create, post_id: commentable, comment: attributes_for(:comment, commentable: commentable, user: @user) } 

     it "saves the new task in the db" do 
     expect{ create_action }.to change{ Comment.count }.by(1) 
     end 
    end 
    end 
end 

require 'rails_helper' 
require 'shared_examples/comments' 
RSpec.describe Posts::CommentsController 
    # ... 
    include_examples "nested comments controller" do 
    let(:commentable) { create(:post, ...) } 
    end 
end 

require 'rails_helper' 
require 'shared_examples/comments' 
RSpec.describe Products::CommentsController 
    # ... 
    include_examples "nested comments controller" do 
    let(:commentable) { create(:product, ...) } 
    end 
end 

我更喜歡另一種方法是使用要求的規格來代替:

require 'rails_helper' 
RSpec.describe "Comments", type: :request do 

    RSpec.shared_example "has nested comments" do 
    let(:path) { polymorphic_path(commentable) + "/comments" } 
    let(:params) { attributes_for(:comment) } 

    describe "POST create" do 
     expect do 
     xhr :post, path, params 
     end.to change(commentable.comments, :count).by(1) 
    end 
    end 


    context "Posts" do 
    include_examples "has nested comments" do 
     let(:commentable) { create(:post) } 
    end 
    end 

    context "Products" do 
    include_examples "has nested comments" do 
     let(:commentable) { create(:product) } 
    end 
    end 
end 

由於您確實發送了HTTP請求而不是僞造它,它們覆蓋了更多的應用程序堆棧。然而,這在測試速度方面具有很小的價格。 shared_context和shared_examples是使RSpec真的很棒的兩件事情。

+0

最大,你是對的,我的路線可以使用沒有嵌套更新/銷燬。我用我的創建操作設置更新了我的答案。我會將註釋創建代碼保留在'CommentsController'中,而不是'Posts :: CommentsController',因爲你可以在更新後的問題中看到。我也有產品評論,所以這樣就可以在模塊控制器中放置'set_post'或'set_product',我可以在'CommentsController'中保留create action的主要部分。你怎麼看?你能告訴我,如果我有一半的代碼在這裏,一半可以測試嗎?順便說一句。在開發環境一切工作正常。 –

+0

最大,請參閱我的prev評論。因此,我只是按照建議的創建操作嵌套我的控制器規範,並且結果如此。 THX –

+0

不錯,它解決了 - 請參閱我的編輯如何測試它的一些技巧。 – max