Validating a Spring (Boot) Response Matches JSON Schema with MockMVC

Featured image for sharing metadata for article

If you're developing an API in Spring (Boot) it's very likely that you're using MockMVC as a means to validate that your web layer is set up correctly.

(If you're not already doing this, give Shift Your Testing Left with Spring Boot Controllers a read!)

Hopefully you're also using a JSON Schema for the Interface Portion of your RESTful API to make sure that you have a well-formed structure for your data models.

But the age-old problem is always being confident that things are actually matching correctly, because it's hard to keep them aligned, even when you're automagically generating them, because there may be optional fields that you're forgetting to set.

Today I've been looking at how you can do this through MockMVC and found that although it's not a first-class citizen (at time of writing) with Spring's ContentResultMatchers, it's actually straightforward to do using Rest Assured's standalone json-schema-validator dependency.

Example

The below code snippets are taken from the example project, which is available on GitLab.

Let's say that we have an API endpoint that can be described by the following JSON Schema:

{
  "$schema": "https://json-schema.org/draft/2019-09/schema",
  "$id": "https://gitlab.com/jamietanna/json-schema-mockmvc/-/tree/main/src/test/resources/schemas/api-response.json",
  "title": "Example API",
  "type": "object",
  "required": [
    "apis"
  ],
  "properties": {
    "apis": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "name",
          "url"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        }
      }
    }
  }
}

If we assume that we've already got tests that perform the following validation:

private ResultActions resultActions;

@BeforeEach
void setup() throws Exception {
  Api api0 = new Api("Contacts API", "https://example.com/contacts");
  Api api1 = new Api("Publishing API", "https://example.com/publishing");
  when(service.findAll()).thenReturn(Set.of(api0, api1));

  resultActions = mvc.perform(get("/apis"));
}

@Test
void containsItems() throws Exception {
  resultActions.andExpect(jsonPath("$.apis").isArray());
}

// ...

If we want to add in a JSON Schema validation test as part of this, what we can do is add a new dependency on the json-schema-validator dependency, and then use the Hamcrest matcher provided:

import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath;

// ...

@Test
void matchesJsonSchema() throws Exception {
  resultActions.andExpect(
      content().string(matchesJsonSchemaInClasspath("schemas/api-response.json")));
}

Which will now give us a handy set of errors if we accidentally break our schema:

Example error
Response content
Expected: The content to match the given JSON schema.
error: instance type (null) does not match any allowed primitive type (allowed: ["string"])
    level: "error"
    schema: {"loadingURI":"file:/home/jamie/workspaces/tmp/json-schema-mockmvc/build/resources/test/schemas/api-response.json#","pointer":"/properties/apis/items/properties/name"}
    instance: {"pointer":"/apis/0/name"}
    domain: "validation"
    keyword: "type"
    found: "null"
    expected: ["string"]
error: instance type (null) does not match any allowed primitive type (allowed: ["string"])
    level: "error"
    schema: {"loadingURI":"file:/home/jamie/workspaces/tmp/json-schema-mockmvc/build/resources/test/schemas/api-response.json#","pointer":"/properties/apis/items/properties/url"}
    instance: {"pointer":"/apis/0/url"}
    domain: "validation"
    keyword: "type"
    found: "null"
    expected: ["string"]

     but: was "{\"apis\":[{\"name\":null,\"url\":null}]}"
java.lang.AssertionError: Response content
Expected: The content to match the given JSON schema.
error: instance type (null) does not match any allowed primitive type (allowed: ["string"])
    level: "error"
    schema: {"loadingURI":"file:/home/jamie/workspaces/tmp/json-schema-mockmvc/build/resources/test/schemas/api-response.json#","pointer":"/properties/apis/items/properties/name"}
    instance: {"pointer":"/apis/0/name"}
    domain: "validation"
    keyword: "type"
    found: "null"
    expected: ["string"]
error: instance type (null) does not match any allowed primitive type (allowed: ["string"])
    level: "error"
    schema: {"loadingURI":"file:/home/jamie/workspaces/tmp/json-schema-mockmvc/build/resources/test/schemas/api-response.json#","pointer":"/properties/apis/items/properties/url"}
    instance: {"pointer":"/apis/0/url"}
    domain: "validation"
    keyword: "type"
    found: "null"
    expected: ["string"]

     but: was "{\"apis\":[{\"name\":null,\"url\":null}]}"

Written by Jamie Tanna's profile image Jamie Tanna on , and last updated on .

Content for this article is shared under the terms of the Creative Commons Attribution Non Commercial Share Alike 4.0 International, and code is shared under the Apache License 2.0.

#blogumentation #spring #spring-boot #java #json-schema.

This post was filed under articles.

Interactions with this post

Interactions with this post

Below you can find the interactions that this page has had using WebMention.

Have you written a response to this post? Let me know the URL:

Do you not have a website set up with WebMention capabilities? You can use Comment Parade.