【问题标题】:RSpec not sending a Post request in JSONRSpec 未在 JSON 中发送 Post 请求
【发布时间】:2016-02-17 17:56:02
【问题描述】:

我正在做一个 Rails 应用程序,它主要是一个 API。我正在尝试为我的 API 端点测试控制器。我的 RSpec Controller 测试器如下:

require 'rails_helper'
require 'spec_helper'
require 'rack/test'
require 'devise'

class RoutesControllerTest < ActionController::TestCase

  describe "User with token"
    test "should post route" do  
      params = { route: {  start_lat: 38.7627951, start_long: -9.1532211,
                            end_lat: 38.7483783, end_long: -9.155045,
                            flag_opt: 0.4, city: 1 }
                }

      post '/routes.json' , params.to_json, format: :json
      assert_response :success
    end
  end
end

我的控制器是:

class RoutesController < ApplicationController

  def create
    city = City.find(params[:route][:city])
    user = current_user
    @route = user.routes.new(route_params)
    @results = @route.calc_route(@route.start_long, @route.start_lat, @route.end_long, @route.end_lat, params[:route][:flag_opt], city)
    if @route.save!
      render :template=>"/routes/routes.json.jbuilder", status: 201, :formats => [:json]
    else
      render json: @route.errors
    end
  end

  private

  def route_params
    json_params = ActionController::Parameters.new( JSON.parse(request.body.read) )
    json_params.require(:route).permit(
          :start_lat, 
          :start_long, 
          :end_lat, 
          :end_long,
          :flag_opt
     )
  end
end

但每次我运行 rspec spec/controller 时都会遇到以下错误:

Failure/Error: json_params = ActionController::Parameters.new(JSON.parse(request.body.read) )

JSON::ParserError:
  757: unexpected token at 'route%5Bcity%5D=24&route%5Bend_lat%5D=41.26171490000001&route%5Bend_long%5D=-8.38193640000001&route%5Bflag_opt%5D=1&route%5Bstart_lat%5D=38.753225&route%5Bstart_long%5D=-9.144376&route%5Buser_id%5D=24'

这意味着请求不是以 JSON 格式发送的

【问题讨论】:

  • 你可以试试这个:post '/routes', params.to_json, { 'Accept' =&gt; Mime::JSON, 'Content-Type' =&gt; Mime::JSON.to_s }
  • 我仍然遇到同样的错误

标签: ruby-on-rails ruby json rspec


【解决方案1】:

我也一直在努力解决这个问题。有时你真的只是想用一个真正的 JSON 体进行测试。我不确定这个解决方案是否特定于 Rails 5,但经过大量的寻找,我终于找到了这个解决方案,我松了一口气。

您可以使用as: :json 代替format: :json 来使RSpec 格式化请求正文。

即改变:

post '/routes.json' , params.to_json, format: :json

post '/routes.json' , params.to_json, as: :json

【讨论】:

  • as: :json 确实是让 Rspec 序列化您的请求所必需的,而不是为您创建通常的 Rails 格式的帖子正文。
  • 这也让我感到意外。在编写 API 文档时在规范中执行 request.raw_post 是导致我来到这里的原因。谢谢,这行得通!
【解决方案2】:

这对我来说可以测试身体上发布到控制器的 json (RSpec 3.7.0)

post :action, params: { whatever: value }, body: { some_key: value }.to_json, as: :json

然后你阅读你的控制器

request.body.read

【讨论】:

    【解决方案3】:

    我很好奇你为什么要json_params = ActionController::Parameters.new( JSON.parse(request.body.read)? (我错过了什么吗?)

    params 应该已经以 ActionContoller::Parameters 的形式出现。所以,route_params 应该是

    def route_params
      params.require(:route).permit(
        :start_lat, 
        :start_long, 
        :end_lat, 
        :end_long,
        :flag_opt
       )
    end
    

    顺便说一句,我假设您的 routes.rb 类似于

    # config/routes.rb
    Rails.application.routes.draw do     
      namespace :api, defaults: {format: 'json'} do
        ...
      end
    end
    

    【讨论】:

    • 你说得很好,这是遗留代码,所以......你知道哈哈
    【解决方案4】:

    您需要将 JSON 解析设置为您的测试环境。

    在 spec/support/request_helper.rb 中创建一个文件:

    module Requests
      module JsonHelpers
        def json
          @json ||= JSON.parse(response.body)
        end
      end
    end
    
    RSpec.configure do |config|
      config.include Requests::JsonHelpers, type: :controller
    end
    

    还有

    【讨论】:

      【解决方案5】:

      我不确定为什么您的 API 必须在其创建操作中接收 json。这就是我要做的:

      class RoutesControllerTest < ActionController::TestCase
      
        describe "User with token"
          test "should post route" do  
            params = { route: {  start_lat: 38.7627951, start_long: -9.1532211,
                                  end_lat: 38.7483783, end_long: -9.155045,
                                  flag_opt: 0.4, city: 1 }
                      }
      
            count = Route.all.size
      
            post :create, params
            expect(response).to have_http_status(201)
            expect(Route.all.size).to eq(count + 1) # actually created something
          end
        end
      end
      

      然后在你的强大参数中:

      def route_params
        params.require(:route).permit(
            :start_lat, 
            :start_long, 
            :end_lat, 
            :end_long,
            :flag_opt
         )
      end
      

      即使在 Rails 端,也无需将所有内容都处理为 JSON。

      【讨论】:

      • 作为旁注,而不是count =expect... to eq (count + 1)expect{post :create, params}.to change{Route.count}.by(1) 怎么样?此外,我属于那个阵营,每次测试只说一个 expect - 所以当某些事情失败时更清楚什么是失败。
      • @jvillian 是的,这是一个改进,我没有想到这一点。保持http_status 期望是一种标准,因为如果请求不成功,那么Route.count 就不会改变。我确实明白,如果它确实发生了变化,那么请求“必须”已经成功,所以也许它有点多余。
      • 我正在开发一个 API 系统,其中 http 状态代码是有意义的。所以,我们肯定会测试它。但是,就在另一个街区。感谢您的反馈!
      【解决方案6】:

      对于 Rails 5,只需在 HTTP 动词前调用 request.accept = "application/json"。示例:

      context "GET #show" do
        it "returns json data" do
          request.accept = "application/json"
          get :show
          hash_body = JSON.parse(response.body)
          #### your expectation here
        end
      end
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-04-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-09-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多