I have the following code in my controller:
format.json { render :json => {
:flashcard => @flashcard,
:lesson => @lesson,
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
There's also the json_spec gem, which is worth a look
https://github.com/collectiveidea/json_spec
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
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
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
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)
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.
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.