How to check for a JSON response using RSpec?

后端 未结 14 1305
谎友^
谎友^ 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: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.

提交回复
热议问题