or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.mdjson-testing.mdtest-infrastructure.md
tile.json

json-testing.mddocs/

JSON Testing

Guide for testing JSON serialization/deserialization with Spring Boot Test AutoConfigure.

Overview

Test slice pattern for isolated, fast JSON tests without full application context.

Key components: @JsonTest, @AutoConfigureJsonTesters, @AutoConfigureJson, JSON Tester beans (JacksonTester, GsonTester, JsonbTester, BasicJsonTester)

Quick Reference: JSON Testing Annotations

AnnotationLoads ContextAuto-Configures TestersUse Case
@JsonTestTest slice (JSON only)YesIsolated JSON testing
@AutoConfigureJsonTestersNo (add to existing test)YesAdd JSON testers to any test
@AutoConfigureJsonNo (add to existing test)NoAdd JSON library config only

@JsonTest

API

package org.springframework.boot.test.autoconfigure.json;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(JsonTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
@OverrideAutoConfiguration(enabled = false)
@TypeExcludeFilters(JsonTypeExcludeFilter.class)
@AutoConfigureJsonTesters
@ImportAutoConfiguration
public @interface JsonTest {
    /**
     * Properties in "key=value" format added to test environment.
     */
    String[] properties() default {};

    /**
     * If true, only @JacksonComponent and JacksonModule beans are included.
     * Set to false to include all beans matched by includeFilters.
     */
    boolean useDefaultFilters() default true;

    /**
     * Additional component filters to include beans.
     */
    Filter[] includeFilters() default {};

    /**
     * Component filters to exclude beans.
     */
    Filter[] excludeFilters() default {};

    /**
     * Auto-configuration classes to exclude.
     */
    @AliasFor(annotation = ImportAutoConfiguration.class, attribute = "exclude")
    Class<?>[] excludeAutoConfiguration() default {};
}

What @JsonTest Loads

Included: @JacksonComponent beans, Jackson modules, JSON library auto-config (Jackson/Gson/JSON-B), JSON tester beans

Excluded: Other auto-configurations (web, database, security), @Component/@Service/@Repository (unless included), @Configuration classes (unless included)

Usage

import org.springframework.boot.test.autoconfigure.json.JsonTest;
import org.springframework.boot.test.json.JacksonTester;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

@JsonTest
public class ProductJsonTests {
    @Autowired
    private JacksonTester<Product> json;

    @Test
    public void testSerialize() throws Exception {
        Product product = new Product("laptop", 999.99, "Electronics");
        assertThat(this.json.write(product))
            .extractingJsonPathStringValue("@.name")
            .isEqualTo("laptop");
        assertThat(this.json.write(product))
            .extractingJsonPathNumberValue("@.price")
            .isEqualTo(999.99);
    }

    @Test
    public void testDeserialize() throws Exception {
        String content = """
            {"name": "laptop", "price": 999.99, "category": "Electronics"}
            """;
        Product product = this.json.parse(content).getObject();
        assertThat(product.getName()).isEqualTo("laptop");
    }

    @Test
    public void testFile() throws Exception {
        assertThat(this.json.write(new Product("laptop", 999.99, "Electronics")))
            .isEqualToJson("expected-product.json");
        Product product = this.json.read("product-sample.json").getObject();
        assertThat(product.getName()).isNotNull();
    }
}

Note: JSON tester fields are automatically initialized by Spring's BeanPostProcessor when @Autowired is used on the field declaration.

Advanced Usage Patterns

Custom Jackson configuration:

@JsonTest(properties = {
    "spring.jackson.serialization.indent-output=true",
    "spring.jackson.serialization.write-dates-as-timestamps=false",
    "spring.jackson.deserialization.fail-on-unknown-properties=false",
    "spring.jackson.default-property-inclusion=non_null"
})
public class ConfiguredJsonTests {}

Include components:

@JsonTest(includeFilters = {
    @Filter(Component.class),
    @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MyJacksonModule.class)
})
public class JsonTestWithCustomComponents {}

Exclude auto-configurations:

@JsonTest(excludeAutoConfiguration = {GsonAutoConfiguration.class, JsonbAutoConfiguration.class})
public class JacksonOnlyJsonTests {}

Disable filters (not recommended):

@JsonTest(useDefaultFilters = false)
public class NoFilterJsonTests {}

Test configuration:

@JsonTest
@Import(CustomJacksonConfiguration.class)
public class CustomConfigJsonTests {}

@AutoConfigureJsonTesters

Add JSON tester auto-configuration to non-JSON test slices.

API

package org.springframework.boot.test.autoconfigure.json;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigureJson
@ImportAutoConfiguration
@PropertyMapping("spring.test.jsontesters")
public @interface AutoConfigureJsonTesters {
    /**
     * Enable or disable JSON tester bean registration.
     * Defaults to true.
     */
    boolean enabled() default true;
}

Usage Patterns

Add JSON Testing to Integration Tests

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters;

@SpringBootTest
@AutoConfigureJsonTesters
public class IntegrationTestWithJson {
    @Autowired
    private JacksonTester<User> json;

    @Autowired
    private UserService userService;  // Full application context available

    @Test
    public void testServiceOutputJson() throws Exception {
        User user = userService.findUser("alice");

        assertThat(this.json.write(user))
            .extractingJsonPathStringValue("@.username")
            .isEqualTo("alice");
    }
}

Add JSON Testing to Other Test Slices

@WebMvcTest(UserController.class)
@AutoConfigureJsonTesters
public class ControllerWithJsonTests {
    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private JacksonTester<User> json;

    @Test
    public void testControllerResponseAndJsonStructure() throws Exception {
        // Test controller
        MvcResult result = mockMvc.perform(get("/users/1"))
            .andExpect(status().isOk())
            .andReturn();

        // Test JSON structure
        String content = result.getResponse().getContentAsString();
        User user = json.parse(content).getObject();
        assertThat(user.getUsername()).isNotNull();
    }
}

Disable testers:

@SpringBootTest
@AutoConfigureJsonTesters(enabled = false)
public class NoJsonTestersTest {}

@AutoConfigureJson

Low-level annotation for JSON library auto-configuration without tester setup.

API

package org.springframework.boot.test.autoconfigure.json;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@ImportAutoConfiguration
public @interface AutoConfigureJson {
}

Usage

For JSON library configuration without testers:

@SpringBootTest
@AutoConfigureJson
public class ManualJsonTests {
    @Autowired
    private ObjectMapper objectMapper;

    @Test
    public void testWithObjectMapper() throws Exception {
        User user = new User("alice");
        String json = objectMapper.writeValueAsString(user);
        assertThat(json).contains("\"username\":\"alice\"");
    }
}

Prefer @JsonTest or @AutoConfigureJsonTesters.

JSON Tester Types

Tester Availability Matrix

TesterLibrary RequiredMaven CoordinatesAuto-Configured When
BasicJsonTesterNoneN/A (Spring Boot built-in)Always
JacksonTesterJacksonorg.springframework.boot:spring-boot-jacksonJackson module on classpath
GsonTesterGsonorg.springframework.boot:spring-boot-gsonGson module on classpath
JsonbTesterJSON-Borg.springframework.boot:spring-boot-jsonbJSON-B module on classpath

Dependency Configuration

IMPORTANT: Spring Boot 4.0.0+ requires Spring Boot module dependencies (not raw library dependencies) for JSON testing.

Required for JacksonTester

<!-- Required for JacksonTester - Spring Boot Jackson module -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-jackson</artifactId>
    <scope>test</scope>
</dependency>

<!-- Required for JSON path assertions (.extractingJsonPathStringValue(), etc.) -->
<dependency>
    <groupId>com.jayway.jsonpath</groupId>
    <artifactId>json-path</artifactId>
    <scope>test</scope>
</dependency>

<!-- Required for JSON tester auto-configuration (@ConditionalOnJsonTesters) -->
<dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-core</artifactId>
    <scope>test</scope>
</dependency>

Gradle:

testImplementation 'org.springframework.boot:spring-boot-jackson'
testImplementation 'com.jayway.jsonpath:json-path'
testImplementation 'org.assertj:assertj-core'

Note: The spring-boot-jackson module includes Jackson 3.x with group ID tools.jackson.core. Do not add raw Jackson dependencies (com.fasterxml.jackson.core) as they may conflict with Spring Boot's Jackson 3.x integration.

GsonTester (Optional)

<!-- For GsonTester support -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-gson</artifactId>
    <scope>test</scope>
</dependency>

Gradle:

testImplementation 'org.springframework.boot:spring-boot-gson'

JsonbTester (Optional)

<!-- For JsonbTester support -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-jsonb</artifactId>
    <scope>test</scope>
</dependency>

Gradle:

testImplementation 'org.springframework.boot:spring-boot-jsonb'

BasicJsonTester

Basic JSON tester that works with any JSON library. Provides assertion methods without serialization.

Defined in: org.springframework.boot.test.json (spring-boot-test module)

@JsonTest
public class BasicJsonTests {
    @Autowired
    private BasicJsonTester basicJson;

    @Test
    public void testJsonStructure() {
        String json = "{\"name\":\"alice\",\"age\":30}";

        assertThat(basicJson.from(json))
            .hasJsonPathStringValue("@.name", "alice");

        assertThat(basicJson.from(json))
            .hasJsonPathNumberValue("@.age", 30);

        assertThat(basicJson.from(json))
            .extractingJsonPathStringValue("@.name")
            .isEqualTo("alice");
    }

    @Test
    public void testJsonFile() {
        assertThat(basicJson.from("user.json"))
            .hasJsonPathValue("@.username");
    }
}

JacksonTester<T>

Tester for Jackson (com.fasterxml.jackson) JSON processing.

Defined in: org.springframework.boot.test.json (spring-boot-test module)

@JsonTest
public class JacksonTests {
    @Autowired
    private JacksonTester<User> json;

    @Test
    public void testWrite() throws Exception {
        User user = new User("alice", 30);

        // Get JSON content
        JsonContent<User> content = this.json.write(user);

        // Assert using AssertJ
        assertThat(content).extractingJsonPathStringValue("@.name")
            .isEqualTo("alice");

        assertThat(content).extractingJsonPathNumberValue("@.age")
            .isEqualTo(30);

        // Get raw JSON string
        String jsonString = content.getJson();

        // Compare to file
        assertThat(content).isEqualToJson("expected-user.json");

        // Check for absence
        assertThat(content).doesNotHaveJsonPath("@.password");
    }

    @Test
    public void testRead() throws Exception {
        String json = "{\"name\":\"alice\",\"age\":30}";

        // Parse JSON
        ObjectContent<User> content = this.json.parse(json);

        // Get parsed object
        User user = content.getObject();

        assertThat(user.getName()).isEqualTo("alice");
        assertThat(user.getAge()).isEqualTo(30);
    }

    @Test
    public void testReadFromFile() throws Exception {
        // Read from classpath file
        User user = this.json.read("user-sample.json").getObject();

        assertThat(user).isNotNull();
    }

    @Test
    public void testReadFromBytes() throws Exception {
        byte[] jsonBytes = "{\"name\":\"alice\"}".getBytes();
        User user = this.json.parse(jsonBytes).getObject();

        assertThat(user.getName()).isEqualTo("alice");
    }
}

GsonTester<T>

Tester for Gson (com.google.gson) JSON processing.

Defined in: org.springframework.boot.test.json (spring-boot-test module)

@JsonTest
public class GsonTests {
    @Autowired(required = false)
    private GsonTester<User> gsonJson;

    @Test
    public void testGson() throws Exception {
        if (gsonJson == null) {
            return;  // Skip if Gson not available
        }

        User user = new User("alice", 30);

        // Write JSON
        JsonContent<User> content = this.gsonJson.write(user);
        assertThat(content.getJson()).contains("\"name\":\"alice\"");

        // Read JSON
        String json = "{\"name\":\"alice\",\"age\":30}";
        User parsed = this.gsonJson.parse(json).getObject();
        assertThat(parsed.getName()).isEqualTo("alice");
    }
}

JsonbTester<T>

Tester for JSON-B (Jakarta JSON Binding) processing.

Defined in: org.springframework.boot.test.json (spring-boot-test module)

@JsonTest
public class JsonbTests {
    @Autowired(required = false)
    private JsonbTester<User> jsonbJson;

    @Test
    public void testJsonb() throws Exception {
        if (jsonbJson == null) {
            return;  // Skip if JSON-B not available
        }

        User user = new User("alice", 30);

        // Write JSON
        String json = this.jsonbJson.write(user).getJson();
        assertThat(json).contains("\"name\":\"alice\"");

        // Read JSON
        String content = "{\"name\":\"alice\",\"age\":30}";
        User parsed = this.jsonbJson.parse(content).getObject();
        assertThat(parsed.getName()).isEqualTo("alice");
    }
}

JSON Tester Assertion Methods

All JSON testers return AssertJ-compatible assertion objects with these methods:

JsonContent Assertions (After write())

JsonContent<T> content = json.write(object);

// Extract and assert JSON path values
assertThat(content).extractingJsonPathStringValue("@.name").isEqualTo("alice");
assertThat(content).extractingJsonPathNumberValue("@.age").isEqualTo(30);
assertThat(content).extractingJsonPathBooleanValue("@.active").isTrue();
assertThat(content).extractingJsonPathArrayValue("@.tags").hasSize(3);
assertThat(content).extractingJsonPathMapValue("@.address").containsKey("city");

// Check for JSON path presence
assertThat(content).hasJsonPathValue("@.name");
assertThat(content).hasJsonPathStringValue("@.name", "alice");
assertThat(content).hasJsonPathNumberValue("@.age", 30);

// Check for JSON path absence
assertThat(content).doesNotHaveJsonPath("@.password");
assertThat(content).doesNotHaveJsonPathValue("@.secretField");

// Compare to expected JSON
assertThat(content).isEqualToJson("{\"name\":\"alice\"}");
assertThat(content).isEqualToJson("expected.json");  // File comparison

// Strict equality (order matters)
assertThat(content).isStrictlyEqualToJson("{\"name\":\"alice\",\"age\":30}");

// Check if JSON is subset of expected
assertThat(content).isEqualToJson("{\"name\":\"alice\"}", JSONCompareMode.LENIENT);

// Get raw JSON string
String jsonString = content.getJson();

ObjectContent Assertions (After parse())

ObjectContent<User> content = json.parse(jsonString);

// Get parsed object
User user = content.getObject();

// Use standard AssertJ assertions on object
assertThat(user).isNotNull();
assertThat(user.getName()).isEqualTo("alice");

Auto-Configuration Classes

JsonTestersAutoConfiguration

package org.springframework.boot.test.autoconfigure.json;

@AutoConfiguration
@ConditionalOnJsonTesters
public final class JsonTestersAutoConfiguration {
    // Registers bean post-processors and factory beans
    // Package-private bean factory methods
}

Registered when:

  1. @ConditionalOnJsonTesters conditions met
  2. Property spring.test.jsontesters.enabled is true (default)
  3. AssertJ is on classpath

Registers:

  • JsonTesterFieldInitializingBeanPostProcessor (initializes @Autowired tester fields)
  • JsonTesterFactoryBean instances for each available JSON library

@ConditionalOnJsonTesters

package org.springframework.boot.test.autoconfigure.json;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ConditionalOnBooleanProperty("spring.test.jsontesters.enabled")
@ConditionalOnClass(name = "org.assertj.core.api.Assert")
public @interface ConditionalOnJsonTesters {
}

Conditions:

  1. Property spring.test.jsontesters.enabled is true (defaults to true via metadata)
  2. AssertJ class org.assertj.core.api.Assert is on classpath

JsonTesterFactoryBean<T, M>

package org.springframework.boot.test.autoconfigure.json;

import org.springframework.beans.factory.FactoryBean;
import org.jspecify.annotations.Nullable;

public final class JsonTesterFactoryBean<T, M> implements FactoryBean<T> {
    public JsonTesterFactoryBean(Class<?> objectType, @Nullable M marshaller);

    @Override
    public T getObject() throws Exception;

    @Override
    public Class<?> getObjectType();

    @Override
    public boolean isSingleton();
}

Type parameters:

  • T: Tester type (JacksonTester, GsonTester, etc.)
  • M: Marshaller type (ObjectMapper, Gson, Jsonb, etc.)

Usage: Internal factory bean for creating JSON tester instances. Not used directly in tests.

JsonMarshalTesterRuntimeHints

package org.springframework.boot.test.autoconfigure.json;

import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.boot.test.json.AbstractJsonMarshalTester;
import org.jspecify.annotations.Nullable;

public abstract class JsonMarshalTesterRuntimeHints implements RuntimeHintsRegistrar {
    /**
     * Create a new JsonMarshalTesterRuntimeHints instance for the given tester class.
     * @param tester the JSON marshal tester class
     */
    protected JsonMarshalTesterRuntimeHints(
            Class<? extends AbstractJsonMarshalTester> tester) {
        // Stores tester class for hint registration
    }

    /**
     * Register runtime hints for GraalVM native image.
     * Registers reflection hints for the tester class declared constructors and the
     * initialize(Class<?>, ResolvableType) method to enable JSON tester functionality
     * in native images.
     * @param hints the runtime hints instance
     * @param classLoader the classloader, or null
     */
    @Override
    public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader);
}

Purpose: Registers GraalVM native image runtime hints for JSON testers to ensure reflection works in native images.

Implementations:

  • JacksonMarshalTesterRuntimeHints
  • GsonMarshalTesterRuntimeHints
  • JsonbMarshalTesterRuntimeHints

Configuration Properties

spring.test.jsontesters.enabled

Type: boolean Default: true

Enable or disable JSON tester auto-configuration.

spring:
  test:
    jsontesters:
      enabled: false

Or in test annotation:

@JsonTest(properties = "spring.test.jsontesters.enabled=false")
public class ManualJsonTests {
    // JSON testers will not be auto-configured
}

Jackson Serialization Properties

Configure Jackson behavior in tests:

spring:
  jackson:
    serialization:
      indent-output: true
      write-dates-as-timestamps: false
      fail-on-empty-beans: false
    deserialization:
      fail-on-unknown-properties: false
    default-property-inclusion: non_null
    date-format: yyyy-MM-dd'T'HH:mm:ss.SSSZ

Or in @JsonTest:

@JsonTest(properties = {
    "spring.jackson.serialization.indent-output=true",
    "spring.jackson.deserialization.fail-on-unknown-properties=false"
})

Troubleshooting

JSON Tester Field is Null

Symptom: JacksonTester field is null or NullPointerException when using it

Cause: JSON tester fields are not Spring beans - they are initialized by a BeanPostProcessor

Solutions:

  1. Use @Autowired - JSON tester fields must be autowired to be initialized:

    @JsonTest
    public class MyJsonTests {
        @Autowired
        private JacksonTester<MyType> json;  // Correct - @Autowired required
    }
  2. Add ALL required dependencies - all three are mandatory:

    <!-- Required for JacksonTester - Spring Boot Jackson module -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-jackson</artifactId>
        <scope>test</scope>
    </dependency>
    
    <!-- Required for JSON path assertions -->
    <dependency>
        <groupId>com.jayway.jsonpath</groupId>
        <artifactId>json-path</artifactId>
        <scope>test</scope>
    </dependency>
    
    <!-- Required for auto-configuration -->
    <dependency>
        <groupId>org.assertj</groupId>
        <artifactId>assertj-core</artifactId>
        <scope>test</scope>
    </dependency>
  3. Verify @JsonTest or @AutoConfigureJsonTesters present:

    @JsonTest  // Required
    public class MyJsonTests {}
  4. Verify property not disabled:

    @JsonTest(properties = "spring.test.jsontesters.enabled=true")

NoClassDefFoundError: com/jayway/jsonpath/Configuration

Symptom: NoClassDefFoundError for json-path classes when using JSON testers

Cause: Missing json-path dependency (required but not documented in older guides)

Solution:

Add the json-path dependency:

<dependency>
    <groupId>com.jayway.jsonpath</groupId>
    <artifactId>json-path</artifactId>
    <scope>test</scope>
</dependency>

Note: JSON path is required for JSON tester assertion methods like .extractingJsonPathStringValue().

Optional JSON Libraries (Gson, JSON-B)

Symptom: Gson or JSON-B tester field is null

Cause: Library not on classpath (these are optional)

Solution:

Check for null before using optional testers:

@JsonTest
public class MultiLibraryTests {
    @Autowired
    private JacksonTester<User> jacksonJson;  // Always available
    @Autowired(required = false)
    private GsonTester<User> gsonJson;  // May be null

    @Test
    public void testGson() {
        if (gsonJson == null) {
            // Gson not available, skip test
            return;
        }
        // Use gsonJson
    }
}

JSON Assertion Failures

Symptom: Assertions fail with unexpected JSON

Solutions:

  1. Debug — Print: System.out.println("Actual: " + content.getJson())
  2. Lenient comparisonassertThat(content).isEqualToJson(expected, JSONCompareMode.LENIENT)
  3. Null fields@JsonTest(properties = "spring.jackson.default-property-inclusion=non_null")

Jackson Module Not Loaded

Symptom: Custom module/serializer not applied

Solutions:

  1. Annotate@JacksonComponent public class CustomModule extends SimpleModule {}
  2. Include filter@JsonTest(includeFilters = @Filter(type = ASSIGNABLE_TYPE, classes = CustomModule.class))
  3. Test config — Register as @Bean in @TestConfiguration

Different Results Between Libraries

Symptom: Different JSON output across libraries

Solutions:

  1. Configure consistently:

    // Jackson: exclude nulls
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public class User {}
    
    // Gson: configure via GsonBuilder bean
    @TestConfiguration
    static class GsonConfig {
        @Bean
        public Gson gson() {
            return new GsonBuilder()
                .serializeNulls(false)
                .create();
        }
    }
  2. Use library-specific annotations:

    @JsonProperty("userName")  // Jackson
    @SerializedName("userName")  // Gson
    private String name;
  3. Test each library separately:

    @JsonTest
    public class JacksonOnlyTests {
        @Autowired
        private JacksonTester<User> json;
        // Test Jackson behavior
    }

Advanced Patterns

Testing Custom Serializers

Test custom Jackson serializers:

@JacksonComponent
public class CustomSerializer extends JsonSerializer<MyType> {
    @Override
    public void serialize(MyType value, JsonGenerator gen, SerializerProvider provider)
            throws IOException {
        gen.writeStartObject();
        gen.writeStringField("customField", value.process());
        gen.writeEndObject();
    }
}

@JsonTest
public class CustomSerializerTests {
    @Autowired
    private JacksonTester<MyType> json;

    @Test
    public void testCustomSerializer() throws Exception {
        MyType obj = new MyType("input");

        assertThat(this.json.write(obj))
            .extractingJsonPathStringValue("@.customField")
            .isEqualTo("processed-input");
    }
}

Testing Date Formatting

Test date serialization formats:

@JsonTest(properties = "spring.jackson.date-format=yyyy-MM-dd")
public class DateFormatTests {
    @Autowired
    private JacksonTester<Event> json;

    @Test
    public void testDateFormat() throws Exception {
        Event event = new Event("meeting", LocalDate.of(2025, 1, 15));

        assertThat(this.json.write(event))
            .extractingJsonPathStringValue("@.date")
            .isEqualTo("2025-01-15");
    }
}

Testing Polymorphic Serialization

Test Jackson polymorphic type handling:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
    @JsonSubTypes.Type(value = Dog.class, name = "dog"),
    @JsonSubTypes.Type(value = Cat.class, name = "cat")
})
public abstract class Animal {}

@JsonTest
public class PolymorphicTests {
    @Autowired
    private JacksonTester<Animal> json;

    @Test
    public void testPolymorphicSerialization() throws Exception {
        Animal dog = new Dog("Buddy");

        assertThat(this.json.write(dog))
            .extractingJsonPathStringValue("@.type")
            .isEqualTo("dog");

        assertThat(this.json.write(dog))
            .extractingJsonPathStringValue("@.name")
            .isEqualTo("Buddy");
    }

    @Test
    public void testPolymorphicDeserialization() throws Exception {
        String json = "{\"type\":\"cat\",\"name\":\"Whiskers\"}";

        Animal animal = this.json.parse(json).getObject();

        assertThat(animal).isInstanceOf(Cat.class);
        assertThat(((Cat) animal).getName()).isEqualTo("Whiskers");
    }
}

Testing with Multiple Object Types

Use multiple testers in one test:

@JsonTest
public class MultiTypeTests {
    @Autowired
    private JacksonTester<User> userJson;
    @Autowired
    private JacksonTester<Product> productJson;
    @Autowired
    private JacksonTester<Order> orderJson;

    @Test
    public void testMultipleTypes() throws Exception {
        User user = new User("alice");
        Product product = new Product("laptop");
        Order order = new Order(user, product);

        // Test each type independently
        assertThat(userJson.write(user)).hasJsonPathValue("@.name");
        assertThat(productJson.write(product)).hasJsonPathValue("@.name");
        assertThat(orderJson.write(order)).hasJsonPathValue("@.user");
    }
}

Testing JSON Views

Test Jackson @JsonView serialization:

public class Views {
    public static class Public {}
    public static class Internal extends Public {}
}

public class User {
    @JsonView(Views.Public.class)
    private String name;

    @JsonView(Views.Internal.class)
    private String email;
}

@JsonTest
public class JsonViewTests {
    @Autowired
    private ObjectMapper objectMapper;

    @Test
    public void testPublicView() throws Exception {
        User user = new User("alice", "alice@example.com");

        String json = objectMapper
            .writerWithView(Views.Public.class)
            .writeValueAsString(user);

        assertThat(json).contains("\"name\":\"alice\"");
        assertThat(json).doesNotContain("\"email\"");
    }

    @Test
    public void testInternalView() throws Exception {
        User user = new User("alice", "alice@example.com");

        String json = objectMapper
            .writerWithView(Views.Internal.class)
            .writeValueAsString(user);

        assertThat(json).contains("\"name\":\"alice\"");
        assertThat(json).contains("\"email\":\"alice@example.com\"");
    }
}

Edge Cases and Corner Cases

Null Handling

@JsonTest
public class NullHandlingTests {
    @Autowired
    private JacksonTester<User> json;

    @Test
    public void testNullField() throws Exception {
        User user = new User("alice", null);  // null email

        // With default config, null fields included
        assertThat(this.json.write(user).getJson())
            .contains("\"email\":null");
    }

    @Test
    public void testExcludeNulls() throws Exception {
        // Configure to exclude nulls
        User user = new User("alice", null);

        // Need custom ObjectMapper or @JsonInclude annotation
        assertThat(this.json.write(user).getJson())
            .doesNotContain("\"email\"");
    }

    @Test
    public void testDeserializeNull() throws Exception {
        String json = "{\"name\":\"alice\",\"email\":null}";

        User user = this.json.parse(json).getObject();

        assertThat(user.getName()).isEqualTo("alice");
        assertThat(user.getEmail()).isNull();
    }
}

Empty Collections

@Test
public void testEmptyArray() throws Exception {
    UserList users = new UserList(Collections.emptyList());

    assertThat(this.json.write(users))
        .extractingJsonPathArrayValue("@.users")
        .isEmpty();
}

@Test
public void testEmptyObject() throws Exception {
    EmptyObject obj = new EmptyObject();

    // May fail with default Jackson config
    // Add property: spring.jackson.serialization.fail-on-empty-beans=false
    String json = this.json.write(obj).getJson();

    assertThat(json).isEqualTo("{}");
}

Special Characters

@Test
public void testSpecialCharacters() throws Exception {
    User user = new User("alice \"the great\"", "test@example.com");

    assertThat(this.json.write(user))
        .extractingJsonPathStringValue("@.name")
        .isEqualTo("alice \"the great\"");  // Quotes properly escaped
}

@Test
public void testUnicodeCharacters() throws Exception {
    User user = new User("José García", "josé@example.com");

    String json = this.json.write(user).getJson();

    assertThat(json).contains("José García");  // Unicode preserved
}

Large Numbers

@Test
public void testLargeNumbers() throws Exception {
    Product product = new Product("item", 9999999999999.99);

    assertThat(this.json.write(product))
        .extractingJsonPathNumberValue("@.price")
        .isEqualTo(9999999999999.99);
}

@Test
public void testBigDecimal() throws Exception {
    // Use BigDecimal for precise decimal values
    Payment payment = new Payment(new BigDecimal("123.456789"));

    String json = this.json.write(payment).getJson();

    assertThat(json).contains("123.456789");  // No precision loss
}

External Type Dependencies

From spring-boot-test module

  • JacksonTester<T>: org.springframework.boot.test.json.JacksonTester
  • GsonTester<T>: org.springframework.boot.test.json.GsonTester
  • JsonbTester<T>: org.springframework.boot.test.json.JsonbTester
  • BasicJsonTester: org.springframework.boot.test.json.BasicJsonTester
  • AbstractJsonMarshalTester: org.springframework.boot.test.json.AbstractJsonMarshalTester
  • JsonContent<T>: org.springframework.boot.test.json.JsonContent
  • ObjectContent<T>: org.springframework.boot.test.json.ObjectContent

From Spring Framework

  • FactoryBean<T>: org.springframework.beans.factory.FactoryBean
  • RuntimeHintsRegistrar: org.springframework.aot.hint.RuntimeHintsRegistrar
  • RuntimeHints: org.springframework.aot.hint.RuntimeHints

From AssertJ

  • Assert: org.assertj.core.api.Assert (required for JSON tester auto-configuration)

From Jackson (when used)

  • ObjectMapper: com.fasterxml.jackson.databind.ObjectMapper
  • Module: com.fasterxml.jackson.databind.Module
  • JsonSerializer: com.fasterxml.jackson.databind.JsonSerializer
  • JsonDeserializer: com.fasterxml.jackson.databind.JsonDeserializer

From Gson (when used)

  • Gson: com.google.gson.Gson
  • GsonBuilder: com.google.gson.GsonBuilder

From JSON-B (when used)

  • Jsonb: jakarta.json.bind.Jsonb
  • JsonbBuilder: jakarta.json.bind.JsonbBuilder