How to check for a JSON response using RSpec?

183,382

Solution 1

You can examine the response object and verify that it contains the expected value:

@expected = { 
        :flashcard  => @flashcard,
        :lesson     => @lesson,
        :success    => true
}.to_json
get :action # replace with action name / params as necessary
response.body.should == @expected

EDIT

Changing this to a post makes it a bit trickier. Here's a way to handle it:

 it "responds with JSON" do
    my_model = stub_model(MyModel,:save=>true)
    MyModel.stub(:new).with({'these' => 'params'}) { my_model }
    post :create, :my_model => {'these' => 'params'}, :format => :json
    response.body.should == my_model.to_json
  end

Note that mock_model will not respond to to_json, so either stub_model or a real model instance is needed.

Solution 2

You could parse the response body like this:

parsed_body = JSON.parse(response.body)

Then you can make your assertions against that parsed content.

parsed_body["foo"].should == "bar"

Solution 3

Building off of Kevin Trowbridge's answer

response.header['Content-Type'].should include 'application/json'

Solution 4

There's also the json_spec gem, which is worth a look

https://github.com/collectiveidea/json_spec

Solution 5

You can also define a helper function inside spec/support/

module ApiHelpers
  def json_body
    JSON.parse(response.body)
  end
end

RSpec.configure do |config| 
  config.include ApiHelpers, type: :request
end

and use json_body whenever you need to access the JSON response.

For example, inside your request spec you can use it directly

context 'when the request contains an authentication header' do
  it 'should return the user info' do
    user  = create(:user)
    get URL, headers: authenticated_header(user)

    expect(response).to have_http_status(:ok)
    expect(response.content_type).to eq('application/vnd.api+json')
    expect(json_body["data"]["attributes"]["email"]).to eq(user.email)
    expect(json_body["data"]["attributes"]["name"]).to eq(user.name)
  end
end
Share:
183,382

Related videos on Youtube

Fizz
Author by

Fizz

Fizz fizz = new Fizz(); fizz.bubble();

Updated on September 03, 2020

Comments

  • Fizz
    Fizz over 3 years

    I have the following code in my controller:

    format.json { render :json => { 
            :flashcard  => @flashcard,
            :lesson     => @lesson,
            :success    => true
    } 
    

    In my RSpec controller test I want to verify that a certain scenario does receive a success json response so I had the following line:

    controller.should_receive(:render).with(hash_including(:success => true))
    

    Although when I run my tests I get the following error:

    Failure/Error: controller.should_receive(:render).with(hash_including(:success => false))
     (#<AnnoController:0x00000002de0560>).render(hash_including(:success=>false))
         expected: 1 time
         received: 0 times
    

    Am I checking the response incorrectly?

  • Fizz
    Fizz about 13 years
    I tried this and unfortunately it says that it got a response of " ". Could this be an error in the controller?
  • Fizz
    Fizz about 13 years
    Also the action is 'create', does it matter than I use a post instead of a get?
  • zetetic
    zetetic about 13 years
    Yes, you'd want post :create with a valid parameters hash.
  • Robert Speicher
    Robert Speicher about 13 years
    You should also be specifying the format you're requesting. post :create, :format => :json
  • tbaums
    tbaums about 13 years
    this seems a lot easier. Thanks.
  • Kevin Bedell
    Kevin Bedell about 12 years
    This library also includes Cucumber step definitions that lok pretty useful.
  • lightyrs
    lightyrs about 12 years
    For render :json => object, I believe Rails returns a Content-Type header of 'application/json'.
  • skalee
    skalee almost 12 years
    JSON is only a string, a sequence of characters and their order matters. {"a":"1","b":"2"} and {"b":"2","a":"1"} are not equal strings which notate equal objects. You should not compare strings but objects, do JSON.parse('{"a":"1","b":"2"}').should == {"a" => "1", "b" => "2"} instead.
  • bricker
    bricker almost 12 years
    Best option I think: response.header['Content-Type'].should match /json/
  • CanCeylan
    CanCeylan over 11 years
    First, thanks a lot. A small correction: JSON.parse(response.body) returns an array. ['foo'] however searches for a key in a hash value. The corrected one is parsed_body[0]['foo'].
  • redjohn
    redjohn over 11 years
    JSON.parse only returns an array if there was an array in the JSON string.
  • PriyankaK
    PriyankaK over 10 years
    JSON.parse(response.body) is returning complete HTML of the page.
  • brentmc79
    brentmc79 over 10 years
    @PriyankaK if it's returning HTML, then your response is not json. Make sure your request is specifying the json format.
  • Dan Garland
    Dan Garland over 10 years
    rspec-rails provides a matcher for this: expect(response.content_type).to eq("application/json")
  • FloatingRock
    FloatingRock almost 10 years
    You could also use b = JSON.parse(response.body, symoblize_names: true) so that you can access them using symbols like so: b[:foo]
  • FloatingRock
    FloatingRock almost 10 years
    Couldn't you just use Mime::JSON instead of 'application/json'?
  • Edgar Ortega
    Edgar Ortega over 9 years
    @FloatingRock I think you will need Mime::JSON.to_s
  • webpapaya
    webpapaya over 8 years
    Like it because it keeps things simple and doesn't add a new dependency.
  • Franklin Yu
    Franklin Yu over 7 years
    Rails 5 made it out of beta, along with #parsed_body. It is not yet documented, but at least JSON format works. Note that the keys are still strings (instead of symbols), so one may find either #deep_symbolize_keys or #with_indifferent_access useful (I like the latter).
  • Philippe Rathé
    Philippe Rathé over 4 years
    Use "response.parsed_body". No need to JSON.parse if you "get foo_url, as: :json".
  • Ryan Taylor
    Ryan Taylor almost 2 years
    even better expect(response.content_type).to start_with('application/json') to avoid the charset attribute that may be present.