Solução de comparação JSON
Rende um Diff limpo, mas potencialmente grande:
actual = JSON.parse(response.body, symbolize_names: true)
expected = { foo: "bar" }
expect(actual).to eq expected
Exemplo de saída do console a partir de dados reais:
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, ...}
(Obrigado a comentar por @floatingrock)
Solução de comparação de cadeias
Se você deseja uma solução revestida de ferro, evite usar analisadores que possam introduzir igualdade de falsos positivos; compare o corpo da resposta com uma sequência. por exemplo:
actual = response.body
expected = ({ foo: "bar" }).to_json
expect(actual).to eq expected
Mas essa segunda solução é menos visualmente amigável, pois usa JSON serializado, o que incluiria muitas aspas vazias.
Solução de correspondência personalizada
Costumo escrever para mim mesmo um correspondente personalizado que faz um trabalho muito melhor de identificar exatamente em qual slot recursivo os caminhos JSON diferem. Adicione o seguinte às suas macros rspec:
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
Exemplo de uso 1:
expect_response(response, :no_content)
Exemplo de uso 2:
expect_response(response, :ok, {
story: {
id: 1,
name: "Shire Burning",
revisions: [ ... ],
}
})
Exemplo de saída:
Type String expected "Shire Burning" but got "Shire Burnin" at path: /:story/:name
Outro exemplo de saída para demonstrar uma incompatibilidade profunda em uma matriz aninhada:
Type Integer expected 2 but got 1 at path: /:story/:revisions[0]/:version
Como você pode ver, a saída informa EXATAMENTE onde corrigir o JSON esperado.