rails + rspec控制器规范与多态关联

时间:2016-05-16 13:00:42

标签: ruby-on-rails rspec controller polymorphic-associations

我试图在控制器规范中测试操作,但由于某种原因,我得到了无路由匹配错误。我该怎么做才能使路线有效?

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
    ...

修改

上面的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

1 个答案:

答案 0 :(得分:3)

使用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以深入解释原因。

在这种情况下将控制器设置为使用继承是一个好主意 - 但是您无法通过控制器规范中的父create类测试CommentsController方法,因为RSpec将始终查看{尝试解决路线时{1}}。

相反,您可能想要使用共享示例:

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

由于您实际上是在发送HTTP请求而不是伪造它,因此它们覆盖了更多的应用程序堆栈。然而,这在测试速度方面具有很小的价格。 shared_context和shared_examples都是让RSpec真棒的两件事。