【问题标题】:ActionController::UrlGenerationError with POST #create action in RSpecActionController::UrlGenerationError 与 RSpec 中的 POST #create 操作
【发布时间】:2017-04-17 23:27:51
【问题描述】:

我正在编写一个电子商务网站,但我的一项测试遇到了问题。我对创建操作的测试如下所示:

require 'rails_helper'

RSpec.describe ReviewsController, type: :controller do
    describe "POST #create" do
        before(:each) do
            user = FactoryGirl.create(:user)
            login user
            @product = FactoryGirl.create(:product)
            post :create, params: { blurb: "Lorem ipsum something or other" }
        end

        it "renders the product path" do
            expect(response).to redirect_to(product_path(@product))
        end
    end
end

“登录用户”行有效,我通过 byebug 确认了这一点(存在有效的用户和会话)。在此迭代中,我收到 No route matches {:action=>"create", :blurb=>"Lorem ipsum something or other", :controller=>"reviews"} 的错误 ,即使我的 rake 路线看起来像这样:

                  Prefix Verb   URI Pattern                                 Controller#Action
                root GET    /                                           landings#index
    new_user_session GET    /users/sign_in(.:format)                    devise/sessions#new
        user_session POST   /users/sign_in(.:format)                    devise/sessions#create
destroy_user_session DELETE /users/sign_out(.:format)                   devise/sessions#destroy
   new_user_password GET    /users/password/new(.:format)               devise/passwords#new
  edit_user_password GET    /users/password/edit(.:format)              devise/passwords#edit
       user_password PATCH  /users/password(.:format)                   devise/passwords#update
                     PUT    /users/password(.:format)                   devise/passwords#update
                     POST   /users/password(.:format)                   devise/passwords#create
cancel_user_registration GET    /users/cancel(.:format)                     devise/registrations#cancel
new_user_registration GET    /users/sign_up(.:format)                    devise/registrations#new
edit_user_registration GET    /users/edit(.:format)                       devise/registrations#edit
user_registration PATCH  /users(.:format)                            devise/registrations#update
                     PUT    /users(.:format)                            devise/registrations#update
                     DELETE /users(.:format)                            devise/registrations#destroy
                     POST   /users(.:format)                            devise/registrations#create
     product_reviews POST   /products/:product_id/reviews(.:format)     reviews#create
  new_product_review GET    /products/:product_id/reviews/new(.:format) reviews#new
             product GET    /products/:id(.:format)                     products#show
             jewelry GET    /jewelry(.:format)                          products#jewelry
            clothing GET    /clothing(.:format)                         products#clothing
         decorations GET    /decorations(.:format)                      products#decorations

当我将帖子行更改为如下所示时: post :create, user: user, params: { blurb: "Lorem ipsum something or other" } 我得到“密钥不存在:用户”。这是我的模型:

class Review < ApplicationRecord
    belongs_to :user
    belongs_to :product

    validates_presence_of :blurb
end

我的控制器:

class ReviewsController < InheritedResources::Base
    respond_to :html
    belongs_to :product
    actions :new

    def create
        @review = Review.new(review_params)
        @product = Product.find(params[:product_id])
        if user_signed_in?
            @review.user_id = current_user.id
            @review.product_id = params[:product_id]
            if @review.save!
                redirect_to product_path(@product)
            else
                render :new
            end
        else
            redirect_to new_user_session_path
        end
    end

    private

    def review_params
        params.require(:review).permit(:blurb, :utf8, :authenticity_token, :commit)
    end
end

还有我的 routes.rb:

Rails.application.routes.draw do
    root 'landings#index'
    devise_for :users
    resources :products, only: [:show] do
        resources :reviews, only: [:new, :create]
    end
    get '/jewelry', to: 'products#jewelry', as: :jewelry
    get '/clothing', to: 'products#clothing', as: :clothing
    get '/decorations', to: 'products#decorations', as: :decorations
end

我正在使用 Ruby 2.4.0 和 Rails 5.1,我们将不胜感激。

//编辑

我将 FactoryGirl::Syntax::Methods 添加到我的 RSpec 配置中,并重构为使用 let 和 product_id,如下所示:

require 'rails_helper'

RSpec.describe ReviewsController, type: :controller do
    let(:user) { create(:user) }
    let(:product) { create(:product) }

    describe "GET #new" do
        before(:each) do
            get :new, params: { product_id: product.id }
        end

        it "renders the new template" do
            expect(response).to render_template(:new)
        end

        it "returns a 200 status code" do
            expect(response).to have_http_status(200)
        end
    end

    describe "POST #create" do
        before do
            login user
        end

        context "with valid attributes" do
            let(:action) do
                post :create, product_id: product, params: { review: FactoryGirl.attributes_for(:review) }
            end

            it "creates a review" do 
                expect { action }.to change(product.reviews, :count).by(+1)
            end

            it "redirects to the review" do
                action
                expect(response).to redirect_to product.reviews.last
            end
        end
    end
end

我没有收到错误“未知关键字:product_id”。我已在控制器参数中将其列入白名单,不知道从哪里开始。

【问题讨论】:

  • belongs_to :product 进入您的模型 - 而不是控制器!
  • 好点!幸运的是,这是我的复制粘贴错误;)
  • 您还需要使用before_action 回调来进行身份验证,而不是在所有控制器上复制相同的逻辑。

标签: ruby-on-rails ruby rspec


【解决方案1】:

您的路线需要 product_id 参数。除此之外,您的测试非常需要适当的结构。如果您想查看如何测试标准 crud 控制器的示例,可以在 rails 中使用 scaffold 命令。

require 'rails_helper'

RSpec.describe ReviewsController, type: :controller do

  # use let to setup given variables
  let(:user) {  FactoryGirl.create(:user) }
  let(:product) {  FactoryGirl.create(:product) }

  describe "POST #create" do
    before do
      login user
    end

    context "with valid attributes" do
      let(:action) do
        post :create, params: { product_id: product, review: FactoryGirl.attributes_for(:review) }
      end

      it "creates a review" do 
        expect { action }.to change(product.reviews, :count).by(+1)
      end

      it "redirects to the review" do
        action
        expect(response).to redirect_to product.reviews.last
      end
    end

    context "with invalid attributes" do
      # ...
    end
  end
end

您应该将身份验证移至前置过滤器并减少重复,您可以将控制器压缩很多:

# call this whatever you want but DRY it out.
class ApplicationController < InheritedResources::Base
  before_action :authenticate!

  def authenticate!
    redirect_to new_user_session_path and return false unless user_signed_in?
  end 
end 

class ReviewsController < ApplicationController
  respond_to :html
  actions :new

  def create
    @product = Product.find(params[:product_id])
    @review = @product.reviews.new(review_params) do |r|
      r.user = current_user
    end
    # don't use .save! here - it will raise an exception
    # if the record is not valid
    if @review.save
      redirect_to product_path(@product)
    else
      render :new
    end
  end

  private

  def review_params
    # only permit the params that actually should be assigned to the model!
    params.require(:review).permit(:blurb) 
  end
end

【讨论】:

  • 感谢语法提示,我很感激。添加 product_id 属性会产生以下错误:“未知关键字:product_id”。
  • 嗯,对了 - 在 rails 5 中,您需要将其放入 params。已编辑。
  • 我把它放在reviews_params,但没有骰子。
  • 不,您需要在规范中的 params 键中传递它。 post :create, params: { product_id: product ....
  • 您似乎也对强参数的作用感到困惑。它基本上可以让您过滤掉应该分配给模型的参数白名单。其余的被丢弃。它不是为了指定允许客户端发送的每个参数。
【解决方案2】:

根据你的动作路线/products/:product_id/reviews(.:format)你需要通过product_id

所以将您的规范更改为:

post :create, product_id: @product.id, params: { blurb: "Lorem ipsum something or other" }

您可能需要在您的ReviewsController 中将actions :new 更改为actions :create


您也可以将config.include FactoryGirl::Syntax::Methods 添加到您的rails_helper.rb,这样您就不需要每次调用FG 方法时都写FactoryGirl

RSpec.configure do |config|
  ...
  config.include FactoryGirl::Syntax::Methods
  ...
end

这里是link to FG docs about that

【讨论】:

  • 添加 product_id 属性会产生以下错误:“未知关键字:product_id”。我会确保它在控制器中被列入白名单并进行处理。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-29
  • 2017-03-29
  • 1970-01-01
相关资源
最近更新 更多