json-fuzzy-match provides assertion to check whether a JSON string fuzzily matches a pattern for JVM languages. This is useful when you test JSON response including dynamic or generated value.
For example, think about testing the following JSON response.
{
"id": "2c0a9fd7-be2c-4bc2-b134-acc3fa13d400", // Generated UUID
"title": "Example Book",
"price": "9.99",
"currency": "USD",
"amount": 10,
"timestamp": "2019-09-25T13:34:17Z" // Dynamic timestamp
}Instead of writing:
val tree = ObjectMapper().readTree(response.content)
assertThat(tree.get("id").textValue()).matches("[0-9a-z-]+")
assertThat(tree.get("title").textValue()).isEqualTo("Example Book")
assertThat(tree.get("price").textValue()).isEqualTo("9.99")
assertThat(tree.get("currency").textValue()).isEqualTo("USD")
assertThat(tree.get("amount").numberValue()).isEqualTo(10)
assertThat(tree.get("timestamp").isTextual).isTrue()you can simply write using json-fuzzy-match:
// language=JSON
JsonStringAssert.assertThat(response.content).jsonMatches("""
{
"id": "#uuid",
"title": "Example Book",
"price": "9.99",
"currency": "USD",
"amount": 10,
"timestamp": "#datetime"
}
""".trimIndent())It is recommended to use json-fuzzy-match with languages which have multi-line string literal such as Kotlin, Scala, Java 13+ and Groovy. Sample codes in this README are written in Kotlin.
json-fuzzy-match is available on Maven Central.
dependencies {
testImplementation("io.github.orangain:json-fuzzy-match:0.7.0")
}<dependencies>
<dependency>
<groupId>io.github.orangain</groupId>
<artifactId>json-fuzzy-match</artifactId>
<version>0.7.0</version>
</dependency>
</dependencies>json-fuzzy-match provides both AssertJ-style and JUnit-style assertions.
// AssertJ-style
import io.github.orangain.jsonmatch.JsonStringAssert
...
JsonStringAssert.assertThat("""{ "foo": "bar" }""").jsonMatches("""{ "foo": "#notnull" }""")// JUnit-style
import io.github.orangain.jsonmatch.JsonMatch.assertJsonMatches
...
assertJsonMatches("""{ "foo": "bar" }""", """{ "foo": "#notnull" }""")In the above examples, the second argument patternJson contains #notnull marker.
The assertion means that value of foo field must exist and not null.
There are several markers as followings:
| Marker | Description |
|---|---|
#ignore |
Skip comparison for this field even if the data element or JSON key is present |
#null |
Expects actual value to be null, and the data element or JSON key must be present |
#notnull |
Expects actual value to be not-null |
#present |
Actual value can be any type or even null, but the key must be present |
#notpresent |
Expects the key to be not present at all |
#array |
Expects actual value to be a JSON array |
#object |
Expects actual value to be a JSON object |
#boolean |
Expects actual value to be a boolean true or false |
#number |
Expects actual value to be a number |
#string |
Expects actual value to be a string |
#uuid |
Expects actual (string) value to conform to the UUID format |
#date |
Expects actual (string) value to conform to the local date in the ISO 8601 extended format |
#datetime |
Expects actual (string) value to conform to the datetime with timezone in the ISO 8601 extended format |
#regex STR |
Expects actual (string) value to match the regular-expression 'STR' (see examples below) |
#[NUM] EXPR |
Advanced array marker. When NUM is provided, array must has length just NUM. When EXPR is provided, array's element must match the pattern 'EXPR' (see examples below) |
You can use double hash ## to mark a field as optional. For example, ##string means that the field can be a string,
null or not present.
| Pattern | {} |
{ "a": null } |
{ "a": "abc" } |
|---|---|---|---|
{ "a": "#ignore" } |
✅ match | ✅ match | ✅ match |
{ "a": "#null" } |
❌ not match | ✅ match | ❌ not match |
{ "a": "#notnull" } |
❌ not match | ❌ not match | ✅ match |
{ "a": "#present" } |
❌ not match | ✅ match | ✅ match |
{ "a": "#notpresent" } |
✅ match | ❌ not match | ❌ not match |
{ "createdOn": "2020-07-23" }matches the pattern{ "createdOn": "#date" }{ "createdOn": "2020/07/23" }does not match the pattern{ "createdOn": "#date" }
{ "createdAt": "2020-07-23T14:56:11+09:00" }matches the pattern{ "createdAt": "#datetime" }{ "createdAt": "2020-07-23T05:56:11Z" }matches the pattern{ "createdAt": "#datetime" }{ "createdAt": "2020-07-23T05:56:11" }does not match the pattern{ "createdAt": "#datetime" }
{ "id": "abc" }matches the pattern{ "id": "#regex [a-z]+" }{ "id": "123" }does not match the pattern{ "id": "#regex [a-z]+" }
{ "tags": ["awesome", "shop"] }matches the following patterns:{ "tags": "#[]" }{ "tags": "#[2]" }{ "tags": "#[] #string" }{ "tags": "#[2] #string" }
MIT License. See LICENSE.
I'm very grateful for the Karate and its authors. The idea of the marker is heavily inspired by the Karate's wonderful fuzzy matching feature. Though json-fuzzy-match does not depend on Karate now, the first version of this library only provided a thin wrapper of Karate's feature. Without it, I was not able to develop this library so quickly.