How to test JSON (de-)serialization in your Spring Boot application with @JsonTest


JSON is a popular data format that is widely used in the communication between systems. Most of the time, I see it being sent with an HTTP request or as the content of a message in a message bus. To ensure the stability of the communication between such systems, it is important that the structure of the JSON does not change without the agreement of both participants. With @JsonTest, Spring Boot contains a handy tool to test the serialization of Java objects into JSON and vice versa.

What exactly is a @JsonTest?

@JsonTest is one of Spring Boot’s test slices. This means it will start an ApplicationContext where everything related to the JSON mapping – and nothing else – is configured exactly according to your application’s runtime. Currently, it supports Jackson, Gson and Jsonb.
If you are using Jackson, the ObjectMapper, all Jackson modules and all components annotated with @JsonComponent will be loaded, as well.

To use @JsonTest, you need to include the dependency org.springframework.boot:spring-boot-starter-test in your project.

pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

Testing the JSON serialization

To give an example, we want to test the correct serialization of the given class Book.

Book.java
public class Book {

    private String title;
    private String author;
    private String isbn;

    //... constructor, getters and setters
}
With the help of an automated @JsonTest, we can make sure that the schema of the serialized object won’t be changed by accident and stays as shown here:

{"title":"...", "author":"...", "isbn":"..."}

The code of our serialization test could look something like this:

BookJsonTest.java
@JsonTest
public class BookJsonTest {

    @Autowired
    private JacksonTester<Book> json;

    @Test
    public void testSerialization() throws Exception {
        var book = new Book("Spring Boot 2", "Michael Simons", "978-3-86490-525-4");
        var expectedJson = "{\"title\":\"Spring Boot 2\",\"author\":\"Michael Simons\",\"isbn\":\"978-3-86490-525-4\"}";

        assertThat(json.write(book)).isEqualToJson(expectedJson);
    }

    // ... test for deserialization
}

The injected JacksonTester is used to expose Asserts which are used in combination with AssertJ to test the JSON content. If you are using Gson or Jsonb, you have to use its sibling GsonTester or JsonbTester.

The Assert returned by isEqualToJson() is used to compare the serialized book with a complete JSON string – in this case, expectedJson.
For more granular testing of single fields, we could also use the hasJsonPathValue() method.

BookJsonTest.java
//...
assertThat(json.write(book)).hasJsonPathValue("$.title", "Spring Boot 2");
//...

Testing the JSON deserialization

If our system receives JSON formatted information, we should make sure that we don’t accidentally break the deserialization of a JSON string into an object. This test can also be automated with the help of @JsonTest.

BookJsonTest.java
@JsonTest
public class BookJsonTest {

    @Autowired
    private JacksonTester<Book> json;

    // ... test for serialization

    @Test
    public void testDeserialization() throws Exception {
        var jsonValue = "{\"title\":\"Spring Boot 2\",\"author\":\"Michael Simons\",\"isbn\":\"978-3-86490-525-4\"}";
        var expectedBook = new Book("Spring Boot 2", "Michael Simons", "978-3-86490-525-4");

        assertThat(json.parse(jsonValue)).usingRecursiveComparison.isEqualTo(expectedBook);
    }
}
As before, the JacksonTester exposes Asserts to test your JSON content. The Assert returned by usingRecursiveComparison() is used for a recursive field by field comparison of the parsed jsonValue with the expectedBook.

Conclusion

The JacksonTester and its siblings provide a great set of Asserts. Use them to make sure that the schemas of the messages that you send to other systems are not altered by accident. If you are receiving messages from other systems, you can use them to ensure that you can always parse these messages.
Thanks to the @JsonTest annotation, the ApplicationContext that is loaded for your integration tests contains only the classes that are needed for the JSON mapping. This can significantly reduce the execution time of your tests.

When not to use it?

If you want to test the JSON response of your @RestController, I would recommend using the @WebMvcTest test slice.

Resources