How to check for a JSON response using RSpec?

后端 未结 14 1278
谎友^
谎友^ 2020-11-29 16:03

I have the following code in my controller:

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


        
相关标签:
14条回答
  • 2020-11-29 16:42

    For Your JSON response you should parse that response for expected results For Instance: parsed_response = JSON.parse(response.body)

    You can check other variables which is included in response like

    expect(parsed_response["success"]).to eq(true)
    expect(parsed_response["flashcard"]).to eq("flashcard expected value")
    expect(parsed_response["lesson"]).to eq("lesson expected value")
    expect(subject["status_code"]).to eq(201)
    

    I prefer also check keys of JSON response, For Example:

    expect(body_as_json.keys).to match_array(["success", "lesson","status_code", "flashcard"])
    

    Here, We can use should matchers For expected results in Rspec

    0 讨论(0)
  • 2020-11-29 16:44

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

    https://github.com/collectiveidea/json_spec

    0 讨论(0)
  • 2020-11-29 16:46

    When using Rails 5 (currently still in beta), there's a new method, parsed_body on the test response, which will return the response parsed as what the last request was encoded at.

    The commit on GitHub: https://github.com/rails/rails/commit/eee3534b

    0 讨论(0)
  • 2020-11-29 16:47

    If you want to take advantage of the hash diff Rspec provides, it is better to parse the body and compare against a hash. Simplest way I've found:

    it 'asserts json body' do
      expected_body = {
        my: 'json',
        hash: 'ok'
      }.stringify_keys
    
      expect(JSON.parse(response.body)).to eql(expected_body)
    end
    
    0 讨论(0)
  • 2020-11-29 16:51

    I found a customer matcher here: https://raw.github.com/gist/917903/92d7101f643e07896659f84609c117c4c279dfad/have_content_type.rb

    Put it in spec/support/matchers/have_content_type.rb and make sure to load stuff from support with something like this in you spec/spec_helper.rb

    Dir[Rails.root.join('spec/support/**/*.rb')].each {|f| require f}
    

    Here is the code itself, just in case it disappeared from the given link.

    RSpec::Matchers.define :have_content_type do |content_type|
      CONTENT_HEADER_MATCHER = /^(.*?)(?:; charset=(.*))?$/
    
      chain :with_charset do |charset|
        @charset = charset
      end
    
      match do |response|
        _, content, charset = *content_type_header.match(CONTENT_HEADER_MATCHER).to_a
    
        if @charset
          @charset == charset && content == content_type
        else
          content == content_type
        end
      end
    
      failure_message_for_should do |response|
        if @charset
          "Content type #{content_type_header.inspect} should match #{content_type.inspect} with charset #{@charset}"
        else
          "Content type #{content_type_header.inspect} should match #{content_type.inspect}"
        end
      end
    
      failure_message_for_should_not do |model|
        if @charset
          "Content type #{content_type_header.inspect} should not match #{content_type.inspect} with charset #{@charset}"
        else
          "Content type #{content_type_header.inspect} should not match #{content_type.inspect}"
        end
      end
    
      def content_type_header
        response.headers['Content-Type']
      end
    end
    
    0 讨论(0)
  • 2020-11-29 16:52

    JSON comparison solution

    Yields a clean but potentially large Diff:

    actual = JSON.parse(response.body, symbolize_names: true)
    expected = { foo: "bar" }
    expect(actual).to eq expected
    

    Example of console output from real data:

    expected: {:story=>{:id=>1, :name=>"The Shire"}}
         got: {:story=>{:id=>1, :name=>"The Shire", :description=>nil, :body=>nil, :number=>1}}
    
       (compared using ==)
    
       Diff:
       @@ -1,2 +1,2 @@
       -:story => {:id=>1, :name=>"The Shire"},
       +:story => {:id=>1, :name=>"The Shire", :description=>nil, ...}
    

    (Thanks to comment by @floatingrock)

    String comparison solution

    If you want an iron-clad solution, you should avoid using parsers which could introduce false positive equality; compare the response body against a string. e.g:

    actual = response.body
    expected = ({ foo: "bar" }).to_json
    expect(actual).to eq expected
    

    But this second solution is less visually friendly as it uses serialized JSON which would include lots of escaped quotation marks.

    Custom matcher solution

    I tend to write myself a custom matcher that does a much better job of pinpointing at exactly which recursive slot the JSON paths differ. Add the following to your rspec macros:

    def expect_response(actual, expected_status, expected_body = nil)
      expect(response).to have_http_status(expected_status)
      if expected_body
        body = JSON.parse(actual.body, symbolize_names: true)
        expect_json_eq(body, expected_body)
      end
    end
    
    def expect_json_eq(actual, expected, path = "")
      expect(actual.class).to eq(expected.class), "Type mismatch at path: #{path}"
      if expected.class == Hash
        expect(actual.keys).to match_array(expected.keys), "Keys mismatch at path: #{path}"
        expected.keys.each do |key|
          expect_json_eq(actual[key], expected[key], "#{path}/:#{key}")
        end
      elsif expected.class == Array
        expected.each_with_index do |e, index|
          expect_json_eq(actual[index], expected[index], "#{path}[#{index}]")
        end
      else
        expect(actual).to eq(expected), "Type #{expected.class} expected #{expected.inspect} but got #{actual.inspect} at path: #{path}"
      end
    end
    

    Example of usage 1:

    expect_response(response, :no_content)
    

    Example of usage 2:

    expect_response(response, :ok, {
      story: {
        id: 1,
        name: "Shire Burning",
        revisions: [ ... ],
      }
    })
    

    Example output:

    Type String expected "Shire Burning" but got "Shire Burnin" at path: /:story/:name
    

    Another example output to demonstrate a mismatch deep in a nested array:

    Type Integer expected 2 but got 1 at path: /:story/:revisions[0]/:version
    

    As you can see, the output tells you EXACTLY where to fix your expected JSON.

    0 讨论(0)
提交回复
热议问题