JSON support

JSON is a common format to interact between machines, as matter of fact, Kest comes with advanced support to that format with a bunch of assertions.
It also comes with two Objects: JsonMap and JsonArray

  • JsonMap is a Map<String, Any?>

  • JsonArray is an array of JsonMap

Matchers

Kest provides several function to check whether a json content matches a Json pattern. To do so, Kest provides by default 3 matchers:

  • {{string}}

  • {{number}}

  • {{boolean}}

For example

{
  "string": "a string",
  "number": 1,
  "boolean": true
}

is matched by

{
  "string": "{{string}}",
  "number": "{{number}}",
  "boolean": "{{boolean}}"
}

or by

{
  "string": "{{string}}",
  "number": 1,
  "boolean": "{{boolean}}"
}

And the function to check that with Kest would be:

json(
    """
    {
      "string": "a string",
      "number": 1,
      "boolean": true
    }"""
) matches validator {
    """
    {
      "string": "{{string}}",
      "number": "{{number}}",
      "boolean": "{{boolean}}"
    }
    """
}

// OR shortcuts are provided two write it without having to remember the notation
json(
    """
    {
      "string": "a string",
      "number": 1,
      "boolean": true
    }"""
) matches validator {
    """
    {
      "string": "$stringPattern",
      "number": "$numberPattern",
      "boolean": "$booleanPattern"
    }
    """
}

Defining your own matchers

If you want to define your own matchers, several possibilities:

Define it textually

val samplePattern = pattern("sample") definedBy """{
  "string": "$stringPattern",
  "number": "$numberPattern",
  "boolean": "$booleanPattern"
}"""

To check match you can now call this:

json(
    """
    {
      "string": "a string",
      "number": 1,
      "boolean": true
    }"""
) matches validator {
    """
    {{sample}}
    """
}

// OR

json(
    """
    {
      "string": "a string",
      "number": 1,
      "boolean": true
    }"""
) matches samplePattern

Define a class

data class Sample(
        val string: String,
        val number: Int,
        val boolean: Boolean
)

Then declare it as a pattern:

val samplePattern = pattern("sample") definedBy Sample::class

To check match you can now call this:

json(
    """
    {
      "string": "a string",
      "number": 1,
      "boolean": true
    }"""
) matches validator {
    """
    {{sample}}
    """
}

// OR

json(
    """
    {
      "string": "a string",
      "number": 1,
      "boolean": true
    }"""
) matches samplePattern

Define a function that will check the validity of pattern

fun checkDateFormat(data: String): Boolean {
    val dateFormatter = DateTimeFormatter.ofPattern("YYYY-MM-dd").withResolverStyle(STRICT)

    return try {
        dateFormatter.parse(data)
        true
    } catch (e: DateTimeParseException) {
        false
    }
}

Then declare it as a pattern:

val samplePattern = pattern("sample") definedBy ::checkDateFormat

To check match you can now call this:

json(
    """
    {
      "date": "2021-01-12",
      "number": 1,
      "boolean": true
    }"""
) matches validator {
    """
    {
      "date": "{{sample}}",
      "number": "$numberPattern",
      "boolean": "$booleanPattern"
    }
    """
}
// OR

json(
    """
    {
      "date": "2021-01-12",
      "number": 1,
      "boolean": true
    }"""
) matches validator {
    """
    {
      "date": "$samplePattern",
      "number": "$numberPattern",
      "boolean": "$booleanPattern"
    }
    """
}

Lists and nullable matchers

Lists

What if you want to check that an array contains a list of objects validating a pattern?

{
  "data": [
    {
      "string": "a string",
      "number": 1,
      "boolean": true
    },
    {
      "string": "another string",
      "number": 2,
      "boolean": false
    }
  ]
}
val samplePattern = pattern("sample") definedBy """{
  "string": "$stringPattern",
  "number": "$numberPattern",
  "boolean": "$booleanPattern"
}"""

To check match you can now call this:

json(
    """
    {
      "data": [
        {
          "string": "a string",
          "number": 1,
          "boolean": true
        },
        {
          "string": "another string",
          "number": 2,
          "boolean": false
        }
      ]
    }"""
) matches validator {
    """
    {
      "data": "[[{{sample}}]]"
    }
    """
}

// OR

json(
    """
    {
      "data": [
        {
          "string": "a string",
          "number": 1,
          "boolean": true
        },
        {
          "string": "another string",
          "number": 2,
          "boolean": false
        }
      ]
    }"""
) matches validator {
    """
    {
      "data": "${jsonArrayOf(samplePattern)}",
      "number": "$numberPattern",
      "boolean": "$booleanPattern"
    }
    """
}

Nullable values

val samplePattern = pattern("sample") definedBy """{
  "string": "${stringPattern.nullable}",
  "number": "$numberPattern",
  "boolean": "$booleanPattern"
}"""

To check match you can now call this:

json(
    """
    {
          "string": null,
          "number": 1,
          "boolean": true
    }"""
) matches validator {
    """
    {
      "string": "{{string?}}",
      "number": "$numberPattern",
      "boolean": "$booleanPattern"
    }
    """
}

// OR

json(
    """
    {
          "string": null,
          "number": 1,
          "boolean": true
    }"""
) matches validator {
    """
    {
      "string": "${stringPattern.nullable}",
      "number": "$numberPattern",
      "boolean": "$booleanPattern"
    }
    """
}

Polymorphism

To go through polyphormism, Kest allows you to define a list of matchers for a given JSON.

Let’s take this example:

{
  "common":  "{{string}}",
  "poly1": "{{string}}"
}
{
  "common":  "{{string}}",
  "poly2": "{{string}}"
}

You can check whether your json matches one or the other of those matchers by passing a list to validator this way:

json(
    """{
          "common":  "a string",
          "poly2": "another string"
        }
    """
) matches validator(
    listOf(
        """{
              "common":  "{{string}}",
              "poly1": "{{string}}"
            }
        """,
        """{
              "common":  "{{string}}",
              "poly2": "{{string}}"
            }
        """
    )
)

Lists

It works all the same for lists!

Tips

For reading data easily from a JsonMap you may use function JsonMap.getForPath(…​) For exemple for a JsonMap representing that Json:

{
    "star": "wars",
    "characters": [
        { "luke": "skywalker" },
        { "han": "solo" },
        { "R2": "D2" }
    ]
}

You may use it like that:

val jsonMap: JsonMap
val name1 = jsonMap.getForPath<String>("characters[0]", "luke") // == "skywalker"
val name2 = jsonMap.getForPath<String>("characters[1]", "han") // == "solo"
val name3 = jsonMap.getForPath<String>("characters[2]", "R2") // == "D2"