【问题标题】:How to send raw post data in a Rails functional test?如何在 Rails 功能测试中发送原始发布数据?
【发布时间】:2010-01-20 18:42:30
【问题描述】:

我希望将原始帖子数据(例如未参数化的 JSON)发送到我的一个控制器进行测试:

class LegacyOrderUpdateControllerTest < ActionController::TestCase
  test "sending json" do
    post :index, '{"foo":"bar", "bool":true}'
  end
end

但这给了我一个NoMethodError: undefined method `symbolize_keys' for #&lt;String:0x00000102cb6080&gt; 错误。

ActionController::TestCase 中发送原始帖子数据的正确方法是什么?

这是一些控制器代码:

def index
  post_data = request.body.read
  req = JSON.parse(post_data)
end

【问题讨论】:

  • 我也很好奇您是如何做到这一点的,以测试基于 JSON 的 API。
  • @Brian 标记为最佳接受答案的答案不再正确。你能更新这个吗?您可能应该选择一个使用 body 参数的答案。这是在 Rails 5 和 Rails 6 中最好的方法。

标签: ruby-on-rails json testing


【解决方案1】:

我今天遇到了同样的问题并找到了解决方案。

在您的test_helper.rb 中,在ActiveSupport::TestCase 中定义以下方法:

def raw_post(action, params, body)
  @request.env['RAW_POST_DATA'] = body
  response = post(action, params)
  @request.env.delete('RAW_POST_DATA')
  response
end

在您的功能测试中,像 post 方法一样使用它,但将原始帖子正文作为第三个参数传递。

class LegacyOrderUpdateControllerTest < ActionController::TestCase
  test "sending json" do
    raw_post :index, {}, {:foo => "bar", :bool => true}.to_json
  end
end

在阅读原始帖子正文时,我在 Rails 2.3.4 上对此进行了测试

request.raw_post

而不是

request.body.read

如果您查看source code,您会发现raw_post 只是将request.body.read 包装起来,并在request env 哈希中检查此RAW_POST_DATA

【讨论】:

  • 嗯,是的,还有 Rails 3.2。谢谢!
  • 确保在控制器中解析 JSON 时使用 request.raw_post (如本答案)而不是 request.body ,否则您会收到关于“无法转换”的奇怪错误StringIO 转换成 String"。
  • 由于某种原因,这并不总是与 Devise 配合得很好 - 调查原因,但它在任何地方都适用于我除了在测试中。
  • 我没有尝试过上述解决方案,但在 Rails 3.2.11 上,这个问题的某个其他答案 (stackoverflow.com/a/4667097/199712) 对我有用。
  • 在 Rails 4 中,这不再是必需的。如果传递给post 的第二个参数是一个字符串,它将被视为RAW_POST_DATA。例如post :create, '{"foo": "bar", "bool": true}'
【解决方案2】:

我实际上只添加了一行就解决了同样的问题 在模拟 rspec 发布请求之前。你做什么 是填充“RAW_POST_DATA”。我试图删除 帖子上的属性变量:创建,但如果我这样做, 它找不到动作。

这是我的解决方案。

def do_create(属性) request.env['RAW_POST_DATA'] = attributes.to_json 发布:创建,属性 结尾

在控制器中,您需要读取 JSON 的代码是 类似的东西

@property = Property.new(JSON.parse(request.body.read))

【讨论】:

  • 太棒了!只需一行,即使没有 attributes 发送到帖子,我也能正常工作。
  • 这适用于 Rails 3.2.x ,但似乎是 hack-ish。 RSpec 应该支持作为 JSON 发布的属性。 Rspec 错误?
  • Rails 4. 对于:create 请求(那些在 url 中不包含参数的请求),不需要重复属性。您可以使用post :create, attrs.to_json 逃脱。对于:update,您可以按照自己的方式进行操作,但RAW_POST_DATA 只需要正文中需要的那些属性,post 只需要 url 中需要的那些属性 (request['RAW_POST_DATA'] = body_attrs.to_json; post :update, url_attrs)。
【解决方案3】:

Rails 5 版本:

post :create, body: '{"foo": "bar", "bool": true}'

参见here - body 字符串参数被视为原始请求正文。

【讨论】:

  • 在 Rails 5.2+ 中工作得非常好。数据可以在控制器中作为 request.raw_post 访问。
  • 在 Rails 6.0 中工作得非常好。 IMO 这应该是公认的答案。
【解决方案4】:

查看运行测试的堆栈跟踪,您可以获得对请求准备的更多控制: ActionDispatch::Integration::RequestHelpers.post => ActionDispatch::Integration::Session.process => Rack::Test::Session.env_for

您可以将 json 字符串作为 :params 传递并指定内容类型“application/json”。在其他情况下,内容类型将设置为“application/x-www-form-urlencoded”,您的 json 将被正确解析。

所以您只需要指定“CONTENT_TYPE”:

post :index, '{"foo":"bar", "bool":true}', "CONTENT_TYPE" => 'application/json'

【讨论】:

  • 这对我不起作用。我收到类似“#<0x00000102b4b0d8>
【解决方案5】:

对于那些使用 Rails5+ 集成测试的人,(未记录的)方法是在 params 参数中传递一个字符串,所以:

post '/path', params: raw_body, headers: { 'Content-Type' => 'application/json' }

【讨论】:

  • 这在 Rails 5.1 中停止为我工作。如果您查看 request.body.read - 它是空的。
【解决方案6】:

我一直在寻找如何在集成测试 (Rails 5.1) 中发布原始 JSON 内容。我想我的解决方案在这种情况下也有帮助。 我查阅了post 方法的文档和源代码:https://api.rubyonrails.org/v5.1/classes/ActionDispatch/Integration/RequestHelpers.html#method-i-post

这将我引导至process 方法以获取更多详细信息:https://api.rubyonrails.org/v5.1/classes/ActionDispatch/Integration/Session.html#method-i-process

多亏了这个,我终于找到了processpost方法接受了哪些参数。 这是我的最终解决方案:

post my_url, params: nil, headers: nil, env: {'RAW_POST_DATA' => my_body_content}, as: :json

【讨论】:

    【解决方案7】:

    如果您使用 RSpec (>= 2.12.0) 并编写请求规范,则包含的模块是 ActionDispatch::Integration::Runner。如果您查看源代码,您会注意到post 方法调用process,它接受rack_env 参数。

    这意味着您可以在规范中简单地执行以下操作:

    #spec/requests/articles_spec.rb
    
    post '/articles', {}, {'RAW_POST_DATA' => 'something'}
    

    在控制器中:

    #app/controllers/articles_controller.rb
    
    def create
      puts request.body.read
    end
    

    【讨论】:

    • 这仅适用于集成测试,不适用于从 ActionController::TestCase 扩展的功能测试。
    【解决方案8】:

    使用 Rails 4,我希望这样做以测试对发布到控制器的原始 xml 的处理。我可以通过将字符串提供给帖子来做到这一点:

    raw_xml = File.read("my_raw.xml")
    post :message, raw_xml, format: :xml
    

    我相信如果提供的参数是一个字符串,它只是作为主体传递给控制器​​。

    【讨论】:

    • 我不知道你为什么会被否决,这是对的。 post 的第二个参数可以是 String 或 IO,这将成为正文。
    • 虽然如果你的 url 还包含参数(例如:update)将不起作用。
    【解决方案9】:

    在 Rails 中,5.1 在执行需要正文中数据的删除请求时为我工作:

    delete your_app_url, as: :json, env: {
       "RAW_POST_DATA" =>  {"a_key" => "a_value"}.to_json
    }
    

    注意:这仅在进行集成测试时有效。

    【讨论】:

    【解决方案10】:

    post 方法需要名称-值对的散列,因此您需要执行以下操作:

    post :index, :data => '{"foo":"bar", "bool":true}'
    

    然后,在您的控制器中,像这样获取要解析的数据:

    post_data = params[:data]
    

    【讨论】:

    • 我试过这个,它需要完全原始,虽然 {"response":"error","errors":"can't parse request: 598: unexpected token at 'data=
    • 您如何在控制器中解析 JSON?你能在你的问题中添加一些控制器代码吗?
    • 我在回答中添加了对您的控制器代码的修改。
    • 这是错误的。如果您的请求正文被声明为 application/json,那么您正在使用 request.body.read 读取控制器中的正文,而 params[:data] 不会做您想做的事。你的 API 也可能不正确。
    • 听起来更像是一个杂牌。你还没有找到一种方法来制作整个身体 application/json,所以你传递了 application/x-www-form-urlencoded,其中包含一个带有 json 值的字段。
    【解决方案11】:

    从 Rails 4.1.5 开始,这是唯一对我有用的东西:

    class LegacyOrderUpdateControllerTest < ActionController::TestCase
      def setup
        @request.headers["Content-Type"] = 'application/json'
      end
    
      test "sending json" do
        post :index, '{"foo":"bar", "bool":true}'.to_json, { account_id: 5, order_id: 10 }
      end
    end
    

    获取位于 /accounts/5/orders/10/items 的网址。这将获取传递的 url 参数以及 JSON 正文。当然,如果没有嵌入订单,那么您可以省略 params 哈希。

    class LegacyOrderUpdateControllerTest < ActionController::TestCase
      def setup
        @request.headers["Content-Type"] = 'application/json'
      end
    
      test "sending json" do
        post :index, '{"foo":"bar", "bool":true}'.to_json
      end
    end
    

    【讨论】:

    • String#to_json 表示您正在传递一个包含 JSON 的 JSON 字符串......这可能不是您想要的。
    【解决方案12】:

    在 Rails 4 中(至少在 4.2.11.3 中)没有简单的方法来测试使用 json 的控制器(功能测试)。对于在运行的服务器中解析 json,ActionDispatch::ParamsParser middleware 负责。控制器测试虽然依赖于 Rack,但它无法解析 json to this day(不应该如此)。

    你可以这样做:

    post :create, body_params.to_json
    

    或:

    post :update, body_parmas.to_json, url_params
    

    body_params 将无法通过params 在控制器中访问。你必须做JSON.parse(request.body.read)。所以唯一想到的是:

    post :update, url_params.merge(body_params)
    

    也就是说,在测试中通过参数(application/x-www-form-urlencoded)传递所有内容。在生产中,主体将由ActionDispatch::ParamsParser 解析以达到相同的效果。除了你的数字变成字符串(可能更多):

    # test/controllers/post_controller_test.rb
    post :update, {id: 1, n: 2}
    
    # app/controller/posts_controller.rb
    def update
        p params  # tests:
                  # {"id"=>"1", "n" => "2", "controller"=>"posts", "action"=>"update"}
                  # production
                  # {"id"=>"1", "n" => 2, "controller"=>"posts", "action"=>"update"}
    end
    

    如果您愿意自己在控制器中解析 json,尽管您可以这样做:

    # test/controllers/post_controller_test.rb
    post_json :update, {n: 2}.to_json, {id: 1}
    
    # app/controller/posts_controller.rb
    def update
        p JSON.parse(request.body.read)  # {"id"=>"1", "n" => 2, "controller"=>"posts", "action"=>"update"}
    end
    

    【讨论】:

      【解决方案13】:
      post :index, {:foo=> 'bar', :bool => 'true'}
      

      【讨论】:

      • 这不是原始 JSON,而是它的散列解释。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-08-16
      • 2014-08-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-10-24
      • 1970-01-01
      相关资源
      最近更新 更多