POSTing raw JSON data with Rails 3.2.11 and RSpec

可紊 提交于 2019-11-28 21:02:35

As far as I have been able to tell, sending raw POST data is no longer possible within a controller spec. However, it can be done pretty easily in a request spec:

describe "Example", :type => :request do
  params = { token: 0 }
  post "/user/reset_password", params.to_json, { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
  #=> params contains { "controller" => "user", "action" => "reset_password", "token" => 0 }
end

This is the way to send raw JSON to a controller action (Rails 3+):

Let's say we have a route like this:

post "/users/:username/posts" => "posts#create"

And let's say you expect the body to be a json that you read by doing:

JSON.parse(request.body.read)

Then your test will look like this:

it "should create a post from a json body" do
  json_payload = '{"message": "My opinion is very important"}'
  post :create, json_payload, {format: 'json', username: "larry" }
end

{format: 'json'} is the magic that makes it happen. Additionally, if we look at the source for TestCase#post http://api.rubyonrails.org/classes/ActionController/TestCase/Behavior.html#method-i-process you can see that it takes the first argument after the action (json_payload) and if it is a string it sets that as raw post body, and parses the rest of the args as normal.

It's also important to point out that rspec is simply a DSL on top of the Rails testing architecture. The post method above is the ActionController::TestCase#post and not some rspec invention.

What we've done in our controller tests is explicitly set the RAW_POST_DATA:

before do
  @request.env['RAW_POST_DATA'] = payload.to_json
  post :my_action
end

Rails 5 example:

RSpec.describe "Sessions responds to JSON", :type => :request do

  scenario 'with correct authentication' do
    params = {id: 1, format: :json}
    post "/users/sign_in", params: params.to_json, headers: { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
    expect(response.header['Content-Type']).to include 'application/json'
  end
end

Here is a full working example of a controller test sending raw json data:

describe UsersController, :type => :controller do

  describe "#update" do
    context 'when resource is found' do
      before(:each) do
        @user = FactoryGirl.create(:user)
      end

      it 'updates the resource with valid data' do
        @request.headers['Content-Type'] = 'application/vnd.api+json'
        old_email = @user.email
        new_email = Faker::Internet.email
        jsondata = 
        {
          "data" => {
            "type" => "users",
            "id" => @user.id,
            "attributes" => {
              "email" => new_email
            }
          }
        }

        patch :update, jsondata.to_json, jsondata.merge({:id => old_id})

        expect(response.status).to eq(200)
        json_response = JSON.parse(response.body)
        expect(json_response['data']['id']).to eq(@user.id)
        expect(json_response['data']['attributes']['email']).to eq(new_email)
      end
    end
  end
end

The important parts are:

@request.headers['Content-Type'] = 'application/vnd.api+json'

and

patch :update, jsondata.to_json, jsondata.merge({:id => old_id})

The first makes sure that the content type is correctly set for your request, this is pretty straightforward. The second part was giving me headaches for a few hours, my initial approach was quite a bit different, but it turned out that there is a Rails bug, which prevents us from sending raw post data in functional tests (but allows us in integration tests), and this is an ugly workaround, but it works (on rails 4.1.8 and rspec-rails 3.0.0).

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!