1. Testing Serialization - Spring Boot vs Micronaut Framework - Building a Rest API

This guide compares how to test serialization and deserialization with Micronaut Framework and Spring Boot.

Authors: Sergio del Amo

Micronaut Version: 4.6.1

1. Sample Project

You can download a sample application with the code examples in this article.

2. Introduction

This guide compares how to test Serialization in a Micronaut Framework and Spring Boot applications.

This guide is the first tutorial of Building a Rest API - a series of tutorials comparing how to develop a REST API with Micronaut Framework and Spring Boot.

3. Record

The application we will build is a REST API for Software as a Service (Saas) subscriptions. The application serializes and deserializes the following Java record from/to JSON.

springboot/src/main/java/example/micronaut/SaasSubscription.java
package example.micronaut;

record SaasSubscription(Long id, String name, Integer cents) {
}

Micronaut Supports Serialization with Jackson Databind or with Micronaut Serialization. If you use Micronaut Jackson Databind, the above record will be identical in Micronaut Framework and Spring Boot.

3.1. Micronaut Serialization

If you use Micronaut Serialization, you must opt in the class to be subject to Serialization, which you can easily do by annotating them with @Serdeable.

micronautframeworkserde/src/main/java/example/micronaut/SaasSubscription.java
package example.micronaut;

import io.micronaut.serde.annotation.Serdeable;

@Serdeable (1)
record SaasSubscription(Long id, String name, Integer cents) {
}
1 Declare the @Serdeable annotation at the type level in your source code to allow the type to be serialized or deserialized.

4. Expected JSON

We want the deserialize from a JSON object, such as into the Java record:

micronautframeworkjacksondatabind/src/test/resources/expected.json
{
  "id": 99,
  "name": "Professional",
  "cents": 4900
}

In the Micronaut application, we add this test resource to src/test/resources/. In the Spring Boot application, we add it to src/test/resources/example/micronaut. Both applications use example/micronaut as the app’s package.

5. Tests

In this tutorial, we use AssertJ in the tests. Moreover, we use Jayway JsonPath - a Java DSL for reading JSON documents.

5.1. Spring Boot Test

Because creating an application context in Spring Boot is slow, integration tests in Spring Boot use Test Slicing. When using test slicing, Spring creates a reduced application context for a specific application slice.

The following tests verify Serialization and deserialization:

springboot/src/test/java/example/micronaut/SaasSubscriptionJsonTest.java
package example.micronaut;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.JsonTest;
import org.springframework.boot.test.json.JacksonTester;

import java.io.IOException;

import static org.assertj.core.api.Assertions.assertThat;

@JsonTest (1)
class SaasSubscriptionJsonTest {

    @Autowired (2)
    private JacksonTester<SaasSubscription> json; (3)

    @Test
    void saasSubscriptionSerializationTest() throws IOException {
        SaasSubscription subscription = new SaasSubscription(99L, "Professional", 4900);
        assertThat(json.write(subscription)).isStrictlyEqualToJson("expected.json");
        assertThat(json.write(subscription)).hasJsonPathNumberValue("@.id");
        assertThat(json.write(subscription)).extractingJsonPathNumberValue("@.id")
                .isEqualTo(99);
        assertThat(json.write(subscription)).hasJsonPathStringValue("@.name");
        assertThat(json.write(subscription)).extractingJsonPathStringValue("@.name")
                .isEqualTo("Professional");
        assertThat(json.write(subscription)).hasJsonPathNumberValue("@.cents");
        assertThat(json.write(subscription)).extractingJsonPathNumberValue("@.cents")
                .isEqualTo(4900);
    }

    @Test
    void saasSubscriptionDeserializationTest() throws IOException {
        String expected = """
           {
               "id":100,
               "name": "Advanced",
               "cents":2900
           }
           """;
        assertThat(json.parse(expected))
                .isEqualTo(new SaasSubscription(100L, "Advanced", 2900));
        assertThat(json.parseObject(expected).id()).isEqualTo(100);
        assertThat(json.parseObject(expected).name()).isEqualTo("Advanced");
        assertThat(json.parseObject(expected).cents()).isEqualTo(2900);
    }
}
1 To test that Object JSON serialization and deserialization is working as expected you can use the @JsonTest annotation. @JsonTest auto-configures Jackson ObjectMapper, any @JsonComponent beans and any Jackson Modules.
2 Inject a bean of type JacksonTester by using @Autowired on the field definition.
3 JacksonTester is an AssertJ based JSON tester backed by Jackson.

5.2. Micronaut Framework Test

Micronaut application context startup is fast. In your tests, you will not employ test slicing. Instead, you will use @MicronautTest. The only customization you can specify with `@MicronautTest' is whether to start an embedded server or not.

micronautframeworkjacksondatabind/src/test/java/example/micronaut/SaasSubscriptionJsonTest.java
package example.micronaut;

import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import io.micronaut.core.io.ResourceLoader;
import io.micronaut.json.JsonMapper;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.stream.Collectors;

import static org.assertj.core.api.Assertions.assertThat;

@MicronautTest(startApplication = false) (1)
class SaasSubscriptionJsonTest {

    @Test
    void saasSubscriptionSerializationTest(JsonMapper json, ResourceLoader resourceLoader) throws IOException { (2)
        SaasSubscription subscription = new SaasSubscription(99L, "Professional", 4900);
        String expected = getResourceAsString(resourceLoader, "expected.json");
        String result = json.writeValueAsString(subscription);
        assertThat(result).isEqualToIgnoringWhitespace(expected);
        DocumentContext documentContext = JsonPath.parse(result);
        Number id = documentContext.read("$.id");
        assertThat(id)
                .isNotNull()
                .isEqualTo(99);

        String name = documentContext.read("$.name");
        assertThat(name)
                .isNotNull()
                .isEqualTo("Professional");

        Number cents = documentContext.read("$.cents");
        assertThat(cents)
                .isNotNull()
                .isEqualTo(4900);
    }

    @Test
    void saasSubscriptionDeserializationTest(JsonMapper json) throws IOException { (2)
        String expected = """
           {
               "id":100,
               "name": "Advanced",
               "cents":2900
           }
           """;
        assertThat(json.readValue(expected, SaasSubscription.class))
                .isEqualTo(new SaasSubscription(100L, "Advanced", 2900));
        assertThat(json.readValue(expected, SaasSubscription.class).id()).isEqualTo(100);
        assertThat(json.readValue(expected, SaasSubscription.class).name()).isEqualTo("Advanced");
        assertThat(json.readValue(expected, SaasSubscription.class).cents()).isEqualTo(2900);
    }

    private static String getResourceAsString(ResourceLoader resourceLoader, String resourceName) {
        return resourceLoader.getResourceAsStream(resourceName)
                .flatMap(stream -> {
                    try (InputStream is = stream;
                         Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8);
                         BufferedReader bufferedReader = new BufferedReader(reader)) {
                        return Optional.of(bufferedReader.lines().collect(Collectors.joining("\n")));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    return Optional.empty();
                })
                .orElse("");
    }
}
1 Annotate the class with @MicronautTest so the Micronaut framework will initialize the application context and the embedded server. By default, each @Test method will be wrapped in a transaction that will be rolled back when the test finishes. This behaviour is is changed by setting transaction to false.
2 By default, with JUnit 5 the test method parameters will be resolved to beans if possible.

6. Conclusion

As shown in this tutorial, testing Serialization in Micronaut Framework and Spring Boot is similar. The main difference is that test slicing is not required in Micronaut Framework.

7. License

All guides are released with an Apache license 2.0 license for the code and a Creative Commons Attribution 4.0 license for the writing and media (images…​).