概要
Play FrameworkのSpecs2でJsonを比較するテストを書いていると、エラーメッセージが分かりにくくて、げんなりした経験ありますでしょうか。
一致しないことはわかるけど、どこの値が一致しないなどがわかりにくかったので、自作してみました。
今回使ったソースは、
https://github.com/2KB/json-matcher
に公開しています。
既存のものと自作のものの比較
既存のものは、こんな感じでJsonの比較をすると
val jsonA = Json.parse(
"""
{
"string_field" : "string",
"number_field" : 100,
"boolean_field" : false,
"null_field" : null,
"array_field" : ["string", 100, false, null],
"object_field" : {"xxx_field" : "xxx", "yyy_field" : 100}
}
""")
// jsonAから、フィールドの順番を変えつつ、xxx_fieldの値を変えている
val jsonB = Json.parse(
"""
{
"number_field" : 100,
"string_field" : "string",
"boolean_field" : false,
"null_field" : null,
"object_field" : {"xxx_field" : "zzz", "yyy_field" : 100},
"array_field" : ["string", 100, false, null]
}
""")
jsonA must be_==(jsonB)
こんなエラーメッセージがでます。
[error] '{"string_field":"string","number_field":100,"boolean_field":false,"null_field":null,"array_field":["string",100,false,null],"object_field":{"xxx_field":"xxx","yyy_field":100}}'
[error]
[error] is not equal to
[error]
[error] '{"number_field":100,"string_field":"string","boolean_field":false,"null_field":null,"object_field":{"xxx_field":"zzz","yyy_field":100},"array_field":["string",100,false,null]}' (CompareJsonMatchErrorMessageSpec.scala:14)
[error] Actual: {"[string]_f...":[]"s...ng[","number]_f...":[100],"...,"[array]_f...":[]"[string",100,fa]l[se,null][object]_f...":[{]"[xxx]_f...":[]"[xxx]",["yyy_]f[ield":100}]}
[error] Expected: {"[number]_f...":[100,]"s...ng[]_f...":["string"],"...,"[object]_f...":[{]"[xxx_fie]l[d":"zzz"],"[yyy]_f...":[100},]"[array]_f...":[]"[string]",[100,]f[alse,null
ちょっとどこ間違っているのかわからないですね。
しかも、一致しているかどうかに関しては、フィールドの順番無視するくせに、どこで間違っているかのメッセージでは、順番そのままで比較するという、残念感。
AとBで違いがあるのは、xxx_field の値だけですので、そこがぱっとわかるようになってほしいものです。
自作したやつだと、エラーメッセージはこんな感じになります。
jsonA must equalToJson(jsonB)
[error] Each json not match.
[error]
[error] Path : object_field->xxx_field
[error] Actual : "xxx"
[error] Expected : "zzz"
[error] -----
[error] Actual detail :
[error] {
[error] "array_field" : [ "string", 100, false, null ],
[error] "boolean_field" : false,
[error] "null_field" : null,
[error] "number_field" : 100,
[error] "object_field" : {
[error] "xxx_field" : "xxx",
[error] "yyy_field" : 100
[error] },
[error] "string_field" : "string"
[error] }
[error]
[error] Expected detail :
[error] {
[error] "array_field" : [ "string", 100, false, null ],
[error] "boolean_field" : false,
[error] "null_field" : null,
[error] "number_field" : 100,
[error] "object_field" : {
[error] "xxx_field" : "zzz",
[error] "yyy_field" : 100
[error] },
[error] "string_field" : "string"
[error] }
[error] -----
[error]
[error] at (CompareJsonMatchErrorMessageSpec.scala:19)
どこが間違っているかはわかりやすくなったかなと思います。
使い方
まずはSpecs2の設定が必要です。build.sbtに、「specs2 % Test」のライブラリを追加しましょう
libraryDependencies ++= Seq(
...
specs2 % Test
)
そして、今回作成したMatcher
https://github.com/2KB/json-matcher/blob/master/test/matchers/JsonMatcher.scala
をダウンロードしてプロジェクトに配置し、
https://github.com/2KB/json-matcher/blob/master/test/CompareJsonMatchErrorMessageSpec.scala
でやっているように、今回作成した
JsonMatcher
を継承して
jsonA must equalToJson(jsonB)
のようにして比較してください。
比較する値は、JsValue型です。
下記のようにJsonの文字列をパースすると,JsValue型になります。
import play.api.libs.json.Json
val jsonVal = Json.parse("""{"xxx" : "hogehoge"}""")
Play Framework以外のSpecs2でも使えるとは思いますが、
その場合はPlay-JSONライブラリも必要かと思います。
https://mvnrepository.com/artifact/com.typesafe.play/play-json_2.11
その他説明など
Matcherの作り方について
今回作ったMatcherは
https://github.com/2KB/json-matcher/blob/master/test/matchers/JsonMatcher.scala
ですが、この作り方が一般的かどうかなどは正直よく分かりません・・・。
Matcherの作り方はあまり情報がなく、情報があっても実際に作ってみると、行数がうまくとれないなどの問題があり、
試行錯誤しているうちになんとなくできたものになりますので、もし作り方に詳しい人がいて、ちょっとこれおかしいんじゃない?
というのがあれば是非ツッコミ頂ければ幸いです。
比較方法詳細
- オブジェクト型のフィールドの順番が違っても、エラーにしない
- 配列型の中身の順番が違った場合は、エラーにする
- フィールドはフィールド名の昇順でソートする
- 最初に見つかったエラーのみを表示する
となっています。
ほんとに比較結果あっているの?
一応テストは作りました。
多分大丈夫だとは思いますが、ミス等あればツッコミよろしくお願い致します。
https://github.com/2KB/json-matcher/blob/master/test/matchers/JsonMatcherSpec.scala