Kotest JSON Matchers
Overview
To use JSON matchers, add testImplementation("io.kotest.extensions:kotest-assertions-json:<version>") to your build.
Basic Matchers
| Matcher | Description | Target |
|---|---|---|
shouldBeValidJson |
Checks that the given string parses as valid JSON. | Multiplatform |
shouldBeJsonObject |
Checks that the string is a valid JSON object. | Multiplatform |
shouldBeJsonArray |
Checks that the string is a valid JSON array. | Multiplatform |
Content-Based Matching
See JSON content matching for more details.
| Matcher | Description | Target |
|---|---|---|
shouldEqualJson |
Checks that a string matches the specified JSON structure. | Multiplatform |
shouldEqualSpecifiedJson |
Checks that a string matches the specified JSON structure while allowing unspecified extra properties. | Multiplatform |
shouldContainJsonKey |
Checks that a string is JSON and contains the specified JSON path. | JVM |
shouldContainJsonKeyValue |
Checks that a string is JSON and contains the specified JSON path with the specified value. | JVM |
shouldMatchJsonResource |
Checks that a string matches the JSON content of the specified test resource. | JVM |
Schema Validation
| Matcher | Description | Target |
|---|---|---|
shouldMatchSchema |
Checks that a String or kotlinx.serialization.JsonElement matches a JsonSchema. See below for schema configuration. |
Multiplatform |
JSON Content Matching
This module is available for JVM and JS targets.
shouldEqualJson
json.shouldEqualJson(other) checks that the JSON structure on the left is the same as the one on the right.
This matcher allows different formatting and different key order.
For example, the following two JSON strings are considered equal:
{
"name": "sam",
"location": "chicago",
"age" : 41
}
and
{ "age" : 41, "name": "sam", "location": "chicago" }
The opposite of this matcher is shouldNotEqualJson, which fails if the two JSON strings are considered equal.
compareJsonOptions
shouldEqualJson supports an additional parameter of type CompareJsonOptions, which provides flags for toggling JSON comparison behavior.
Usage:
Options can be specified inline:
a.shouldEqualJson(b, compareJsonOptions { arrayOrder = ArrayOrder.Strict })
Another option is to define the comparison behavior as desired:
val myOptions = compareJsonOptions {
typeCoercion = TypeCoercion.Enabled
arrayOrder = ArrayOrder.Lenient
}
infix fun String.lenientShouldEqualJson(other: String) = this.shouldEqualJson(other, myOptions)
"[1, 2]" lenientShouldEqualJson "[2, 1]" // This will pass
Parameters
| Name | Purpose | Possible values | Default |
|---|---|---|---|
PropertyOrder |
Determines whether JSON object property order is considered during comparison. | PropertyOrder.Strict, PropertyOrder.Lenient |
PropertyOrder.Lenient; property order does not matter. |
ArrayOrder |
Determines whether JSON array element order is considered during comparison. | ArrayOrder.Strict, ArrayOrder.Lenient |
ArrayOrder.Strict; element order matters. |
FieldComparison |
Determines whether comparison fails when actualJSON contains additional properties compared with expected. |
FieldComparison.Strict, FieldComparison.Lenient |
FieldComparison.Strict; additional properties make the values unequal. |
NumberFormat |
Determines whether number comparison is strict about number format, for example whether 100.0 and 100 are considered equal. |
NumberFormat.Strict, NumberFormat.Lenient |
NumberFormat.Lenient; number format does not matter. |
TypeCoercion |
Determines whether types are coerced, for example when a string contains a number or boolean value. | TypeCoercion.Enabled, TypeCoercion.Disabled |
TypeCoercion.Disabled; types are not coerced. |
Target: Multiplatform
shouldEqualSpecifiedJson
This is an alias for shouldEqualJson, except the default option for FieldComparison is set to FieldComparison.Lenient.
val a = """ { "a": true, "date": "2019-11-03" } """
val b = """ { "a": true } """
// this would pass
a shouldEqualSpecifiedJson b
// this would fail
a shouldEqualJson b
Target: Multiplatform
shouldContainJsonKey
json.shouldContainJsonKey("$.json.path") checks that a JSON string contains the specified JSON path.
The opposite matcher is shouldNotContainJsonKey, which fails if the JSON string contains the specified JSON path.
Target: JVM
shouldContainJsonKeyValue
str.shouldContainJsonKeyValue("$.json.path", value) checks that a JSON string contains a JSON path with the specified value.
The opposite matcher is shouldNotContainJsonKeyValue, which fails if the JSON path contains the specified value.
Target: JVM
shouldMatchJsonResource
json.shouldMatchJsonResource("/file.json") checks that the JSON is the same as the existing test resource /file.json, ignoring property order and formatting.
Target: JVM
JSON Schema Matchers
| Matcher | Description | Target |
|---|---|---|
shouldMatchSchema |
Checks that a String or kotlinx.serialization.JsonElement matches a JsonSchema. See below for schema configuration. |
Multiplatform |
A subset of JSON Schema can be defined by parsing a text schema.
Example:
val parsedSchema = parseSchema(
"""
{
"$id": "https://example.com/geographical-location.schema.json", // will be ignored
"$schema": "https://json-schema.org/draft/2020-12/schema", // will be ignored
"title": "Longitude and Latitude Values", // will be ignored
"description": "A geographical coordinate.", // will be ignored
"required": [ "latitude", "longitude" ],
"type": "object",
"properties": {
"latitude": {
"type": "number",
"minimum": -90,
"maximum": 90
},
"longitude": {
"type": "number",
"minimum": -180,
"maximum": 180
}
}
}
"""
)
Or use Kotest’s built-in DSL:
val addressSchema = jsonSchema {
obj { // object is reserved, obj was chosen over jsonObject for brevity but could be changed ofc, or jsonObject could be added as alternative.
withProperty("street", required = true) { string() }
withProperty("zipCode", required = true) {
integer {
beEven() and beInRange(10000..99999) // supports constructing a matcher that will be used to test values
}
}
additionalProperties = false // triggers failure if other properties are defined in actual
}
}
val personSchema = jsonSchema {
obj {
withProperty("name", required = true) { string() }
withProperty("address") { addressSchema() } // Schemas can re-use other schemas 🎉
}
}
Building Schemas
Arrays
Arrays are used for ordered elements. In JSON, each element in an array can have a different type.
Length (minItems and maxItems)
Array length can be specified with the minItems and maxItems keywords. Each keyword value must be a non-negative number; the defaults are 0 and Int.MAX_VALUE.
val lengthBoundedSchema = jsonSchema {
array(minItems = 0, maxItems = 1) { number() }
}
Uniqueness
A schema can check that every item in an array is unique. Just set the uniqueItems keyword to true.
val uniqueArray = jsonSchema {
array(uniqueItems = true) { number() }
}
⚠️ Kotest currently supports only a subset of JSON Schema. The following items are not currently supported:
- $defs and $refs
- Recursive schemas
- Parsing of schema composition
- string.format
- array.prefixItems,
- array.contains,
- array.items = false
- array.maxContains
- array.minContains
- array.uniqueItems
- enum
Validation
Once a schema is defined, it can be used to validate a String or kotlinx.serialization.JsonElement:
"{}" shouldMatchSchema personSchema
// fails with:
// $.name => Expected string, but was undefined
""" { "name": "Emil", "age": 34 } """
// Passes, since address isn't required and `additionalProperties` are allowed