Spring Boot provides a set of annotations you can use for writing tests for a specific part of your application. These annotations take care of auto-configuring Spring's ApplicationContext
to include all beans you need. The most known ones are those of testing your web layer or database layer in isolation. There is also a lesser-known annotation for testing JSON serialization with either Jackson, Gson, or Jsonb: @JsonTest.
Spring Boot Project Setup
The demo project is using Java 11, Spring Boot 2.4, and two Spring Boot Starters:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <?xml version="1.0" encoding="UTF-8"?> <project> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.5</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>de.rieckpil.blog</groupId> <artifactId>testing-json-serialization-spring</artifactId> <version>0.0.1-SNAPSHOT</version> <name>testing-json-serialization-spring</name> <description>Testing JSON serialization with Spring Boot</description> <properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <!-- as of Spring Boot 2.4, the JUnit Vintage Engine is no longer included --> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> |
The relevant Jackson dependencies are part of the Spring Boot Starter JSON, which is a transitive dependency of Spring Boot Starter Web:
1 2 3 4 5 6 7 8 9 | [INFO] de.rieckpil.blog:testing-json-serialization-spring:jar:0.0.1-SNAPSHOT [INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.4.5:compile [INFO] | +- org.springframework.boot:spring-boot-starter-json:jar:2.4.5:compile [INFO] | | +- com.fasterxml.jackson.core:jackson-databind:jar:2.11.4:compile [INFO] | | | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.11.4:compile [INFO] | | | \- com.fasterxml.jackson.core:jackson-core:jar:2.11.4:compile [INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.11.4:compile [INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.11.4:compile [INFO] | | \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.11.4:compile |
Please note that all upcoming test examples use JUnit Jupiter (part of JUnit 5). If your application is using JUnit 4, add the @RunWith(SpringRunner.class)
to each test class.
Test the JSON Serialization From Jackson
Let's start with a basic example and assume our codebase contains the following object to transfer data about a user:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @JsonNaming(PropertyNamingStrategy.LowerCaseStrategy.class) public class UserDetails { private Long id; private String firstName; private String lastName; @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd.MM.yyyy") private LocalDate dateOfBirth; @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) private boolean enabled; // ... getters & setters } |
As remote systems rely on the API contract, we should add a test to verify code changes in the future don't break the data format.
This example includes Jackson-specific annotations to manipulate the JSON representation of this class. Using @JsonFormat
we can configure how to display the date and @JsonProperty
manages the accessibility of the enabled field. JsonProperty.Access.WRITE_ONLY
tells Jackson not to include the value while serializing the object but expect it when deserializing a JSON string back to this object.
All we need for the test setup with Spring Boot is the following:
1 2 3 4 5 6 7 8 9 | @JsonTest class UserDetailsJsonTest { @Autowired private JacksonTester<UserDetails> json; // ... tests } |
The @JsonTest
annotation ensures to auto-configure a Spring TestContext with only a subset of Spring Beans required to test the JSON serialization. Anything related to our web-layer or persistence-layer is not part of this sliced application context.
Besides the JacksonTester
, there is also JsonbTester
and GsonTester
if your application is not using Jackson.
Next, let's write the actual test to verify the serialization to JSON:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Test public void testSerialize() throws Exception { UserDetails userDetails = new UserDetails(1L, "Duke", "Java", LocalDate.of(1995, 1, 1), true); JsonContent<UserDetails> result = this.json.write(userDetails); assertThat(result).hasJsonPathStringValue("$.firstname"); assertThat(result).extractingJsonPathStringValue("$.firstname").isEqualTo("Duke"); assertThat(result).extractingJsonPathStringValue("$.lastname").isEqualTo("Java"); assertThat(result).extractingJsonPathStringValue("$.dateofbirth").isEqualTo("01.01.1995"); assertThat(result).doesNotHaveJsonPath("$.enabled"); } |
Given the AssertJ JSON assertion support, we can verify the output by accessing the attributes with JSON Path expressions.
Furthermore, the JacksonTester
can also be used to test the deserialization:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @Test public void testDeserialize() throws Exception { String jsonContent = "{\"firstname\":\"Mike\", \"lastname\": \"Meyer\"," + " \"dateofbirth\":\"15.05.1990\"," + " \"id\": 42, \"enabled\": true}"; UserDetails result = this.json.parse(jsonContent).getObject(); assertThat(result.getFirstName()).isEqualTo("Mike"); assertThat(result.getLastName()).isEqualTo("Meyer"); assertThat(result.getDateOfBirth()).isEqualTo(LocalDate.of(1990, 05, 15)); assertThat(result.getId()).isEqualTo(42L); assertThat(result.isEnabled()).isEqualTo(true); } |
Testing JSON Serialization With Custom @JsonComponent Beans
As part of the auto-configuration triggered by @JsonTest
, @JsonComponent
beans are also included in the application context of the test. This annotation allows us to mark custom JsonSerializer
or JsonDeserializer
to be automatically picked up by Jackson's ObjectMapper
.
As a quick demo for this, consider we have the following class:
1 2 3 4 5 6 7 8 | public class CarDetails { private String manufacturer; private String type; private String color; // ... } |
And we use a custom JsonSerializer
for serializing objects of this class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @JsonComponent public class CarDetailsJsonSerializer extends JsonSerializer<CarDetails> { @Override public void serialize( CarDetails value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException { jsonGenerator.writeStartObject(); jsonGenerator.writeStringField( "type", value.getManufacturer() + "|" + value.getType() + "|" + value.getColor()); jsonGenerator.writeEndObject(); } } |
We can test this without any further setup using @JsonTest
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @JsonTest class CarDetailsJsonTest { @Autowired private JacksonTester<CarDetails> json; @Test public void testSerialize() throws Exception { CarDetails carDetails = new CarDetails("Audi", "A3", "gray"); JsonContent<CarDetails> result = this.json.write(carDetails); System.out.println(result); assertThat(result).extractingJsonPathStringValue("$.type").contains("Audi", "A3", "gray"); } } |
Apart from @JsonTest
, Spring Boot provides more annotations to test different parts of our application in isolation. You can find an overview of all common Spring Boot test slice annotations plus their usage and set up as part of this blog post.
You can find the source code for these JSON serialization test examples on GitHub.
Here's a list of further tutorials and resources to learn about Spring Boot's excellent test support:
- Guide to Testing Spring Boot Applications With MockMvc
- Test Your Spring Boot JPA Persistence Layer With @DataJpaTest
- MongoDB Testcontainers Setup for @DataMongoTest
Have fun testing your JSON serialization with @JsonTest,
Phil