2016-05-16 31 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