orangain flavor

じっくりコトコト煮込んだみかん2。知らないことを知りたい。

動的な値を含むJSON文字列をテストするための json-fuzzy-match を作った

APIなどのテストで、JSONが意図した形式・値になっているかチェックしたいことがあります。

文字列として比較してチェックする場合、JSONに含まれる空白がちょっと変わっただけでエラーになりますし、チェック対象のJSON文字列が整形されていないとテストの可読性が落ちます。さらに自動生成されるIDや更新日時など、動的な値が含まれていると使えません*1

JSON文字列をパースしてから比較する場合、Jacksonの ObjectMapper#readValue() のような気の利いたデシリアライズ機能を使うと、本当にJSON文字列として意図した形になっているか自信が持てません。一方で、 ObjectMapper#readTree() のように木構造としてパースする機能を使うと、記述が冗長になりがちで面倒です。テストコードも読みやすくありません。

json-fuzzy-match

そこで、JVM系言語においてJSON文字列をいい感じに比較できるアサーションライブラリ「json-fuzzy-match」を作りました。スペースや改行などのフォーマットの違いを無視して比較できるだけでなく、 #string #number #uuid のようなマーカーを使って、動的な値を含むJSONをそのまま比較できます。

例えばテスト対象とする response.content が次のようなJSON文字列だとします。

{
    "id": "2c0a9fd7-be2c-4bc2-b134-acc3fa13d400", // 自動生成されたUUID
    "title": "Example Book",
    "price": "9.99",
    "currency": "USD",
    "amount": 10,
    "timestamp": "2019-09-25T13:34:17Z" // 動的なタイムスタンプ
}

json-fuzzy-matchを使うと、次のように比較できます*2。UUIDやタイムスタンプのような動的な項目にはマーカーを指定しているので、形式が合っていればテストが通ります。

JsonStringAssert.assertThat(response.content).jsonMatches("""
    {
      "id": "#uuid",
      "title": "Example Book",
      "price": "9.99",
      "currency": "USD",
      "amount": 10,
      "timestamp": "#string"
    }
""".trimIndent())

インストール方法やマーカーのリファレンスなど、詳しくはGitHubリポジトリを参照してください。

github.com

謝辞

json-fuzzy-matchは Karate というテスト自動化ツールの機能の一つである Fuzzy MatchingJUnitやAssertJから使いやすいようにラップしただけのライブラリです。使えるマーカーもKarateのそのままです。

JSONのテストを書くのは面倒で、日頃から良い方法がないか考えていた中でKarateのFuzzy Matchingに出会いました。このような便利なツールがOSSとして公開されていたことで、JSONのテストを簡単に書けるようになったので、Karateの開発者には非常に感謝しています。この場を借りてお礼申し上げます。

最後に

しばらく勤務先で使っていますが、それなりに便利だと思うので、良ければぜひ使ってみてください。改善点などあれば、IssueやPull Requestなどいただけると嬉しいです。

あるいは、JSONをテストするためのもっと良い方法をご存知であれば、ぜひ教えていただけるとありがたいです。

*1:正規表現で頑張る手もありますが、あまりやりたくありません。

*2:この例はAssertJスタイルのアサーションを使っていますが、古き良きJUnitスタイルの assertJsonMatches() も提供しています。