From af941e0a9a90e982adb8d46049dbdf8d3ae673af Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Fri, 26 Sep 2025 15:05:14 +0200 Subject: [PATCH] Add nullability annotations to tests in module/spring-boot-jackson See gh-47263 --- module/spring-boot-jackson/build.gradle | 4 +++ .../jackson/JsonComponentModuleTests.java | 30 ++++++++++++------- .../boot/jackson/JsonMixinModuleTests.java | 17 +++++++---- .../boot/jackson/NameAndAgeJsonComponent.java | 3 ++ .../jackson/ObjectValueDeserializerTests.java | 12 +++++--- .../JacksonAutoConfigurationTests.java | 29 +++++++++--------- .../boot/jackson/types/Name.java | 8 +++-- .../boot/jackson/types/NameAndAge.java | 6 ++-- 8 files changed, 71 insertions(+), 38 deletions(-) diff --git a/module/spring-boot-jackson/build.gradle b/module/spring-boot-jackson/build.gradle index af34f2bab4c..10c6fafdba8 100644 --- a/module/spring-boot-jackson/build.gradle +++ b/module/spring-boot-jackson/build.gradle @@ -39,3 +39,7 @@ dependencies { testImplementation(project(":test-support:spring-boot-test-support")) testRuntimeOnly("ch.qos.logback:logback-classic") } + +tasks.named("compileTestJava") { + options.nullability.checking = "tests" +} diff --git a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonComponentModuleTests.java b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonComponentModuleTests.java index ffccec3d0b7..57bee72c4ad 100644 --- a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonComponentModuleTests.java +++ b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonComponentModuleTests.java @@ -19,6 +19,7 @@ package org.springframework.boot.jackson; import java.util.HashMap; import java.util.Map; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import tools.jackson.core.JacksonException; @@ -31,6 +32,7 @@ import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.aot.test.generate.TestGenerationContext; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.jackson.JsonComponentModule.JsonComponentBeanFactoryInitializationAotProcessor; import org.springframework.boot.jackson.JsonComponentModuleTests.ComponentWithInnerAbstractClass.AbstractSerializer; @@ -43,6 +45,7 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.Mockito.mock; /** * Tests for {@link JsonComponentModule}. @@ -53,7 +56,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; */ class JsonComponentModuleTests { - private AnnotationConfigApplicationContext context; + private @Nullable AnnotationConfigApplicationContext context; @AfterEach void closeContext() { @@ -65,21 +68,21 @@ class JsonComponentModuleTests { @Test void moduleShouldRegisterSerializers() throws Exception { load(OnlySerializer.class); - JsonComponentModule module = this.context.getBean(JsonComponentModule.class); + JsonComponentModule module = getContext().getBean(JsonComponentModule.class); assertSerialize(module); } @Test void moduleShouldRegisterDeserializers() throws Exception { load(OnlyDeserializer.class); - JsonComponentModule module = this.context.getBean(JsonComponentModule.class); + JsonComponentModule module = getContext().getBean(JsonComponentModule.class); assertDeserialize(module); } @Test void moduleShouldRegisterInnerClasses() throws Exception { load(NameAndAgeJsonComponent.class); - JsonComponentModule module = this.context.getBean(JsonComponentModule.class); + JsonComponentModule module = getContext().getBean(JsonComponentModule.class); assertSerialize(module); assertDeserialize(module); } @@ -96,21 +99,21 @@ class JsonComponentModuleTests { @Test void moduleShouldRegisterKeySerializers() throws Exception { load(OnlyKeySerializer.class); - JsonComponentModule module = this.context.getBean(JsonComponentModule.class); + JsonComponentModule module = getContext().getBean(JsonComponentModule.class); assertKeySerialize(module); } @Test void moduleShouldRegisterKeyDeserializers() throws Exception { load(OnlyKeyDeserializer.class); - JsonComponentModule module = this.context.getBean(JsonComponentModule.class); + JsonComponentModule module = getContext().getBean(JsonComponentModule.class); assertKeyDeserialize(module); } @Test void moduleShouldRegisterInnerClassesForKeyHandlers() throws Exception { load(NameAndAgeJsonKeyComponent.class); - JsonComponentModule module = this.context.getBean(JsonComponentModule.class); + JsonComponentModule module = getContext().getBean(JsonComponentModule.class); assertKeySerialize(module); assertKeyDeserialize(module); } @@ -118,7 +121,7 @@ class JsonComponentModuleTests { @Test void moduleShouldRegisterOnlyForSpecifiedClasses() throws Exception { load(NameAndCareerJsonComponent.class); - JsonComponentModule module = this.context.getBean(JsonComponentModule.class); + JsonComponentModule module = getContext().getBean(JsonComponentModule.class); assertSerialize(module, new NameAndCareer("spring", "developer"), "{\"name\":\"spring\"}"); assertSerialize(module, NameAndAge.create("spring", 100), "{\"age\":100,\"name\":\"spring\"}"); assertDeserializeForSpecifiedClasses(module); @@ -127,11 +130,12 @@ class JsonComponentModuleTests { @Test void aotContributionRegistersReflectionHintsForSuitableInnerClasses() { load(ComponentWithInnerAbstractClass.class); - ConfigurableListableBeanFactory beanFactory = this.context.getBeanFactory(); + ConfigurableListableBeanFactory beanFactory = getContext().getBeanFactory(); BeanFactoryInitializationAotContribution contribution = new JsonComponentBeanFactoryInitializationAotProcessor() .processAheadOfTime(beanFactory); TestGenerationContext generationContext = new TestGenerationContext(); - contribution.applyTo(generationContext, null); + assertThat(contribution).isNotNull(); + contribution.applyTo(generationContext, mock(BeanFactoryInitializationCode.class)); RuntimeHints runtimeHints = generationContext.getRuntimeHints(); assertThat(RuntimeHintsPredicates.reflection().onType(ComponentWithInnerAbstractClass.class)) .accepts(runtimeHints); @@ -199,6 +203,12 @@ class JsonComponentModuleTests { assertThat(map).containsEntry(NameAndAge.create("spring", 100), true); } + private AnnotationConfigApplicationContext getContext() { + AnnotationConfigApplicationContext context = this.context; + assertThat(context).isNotNull(); + return context; + } + @JsonComponent static class OnlySerializer extends NameAndAgeJsonComponent.Serializer { diff --git a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonMixinModuleTests.java b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonMixinModuleTests.java index 43ffbd66a48..8fba4cea39a 100644 --- a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonMixinModuleTests.java +++ b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonMixinModuleTests.java @@ -19,6 +19,7 @@ package org.springframework.boot.jackson; import java.util.Arrays; import java.util.List; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import tools.jackson.databind.JacksonModule; @@ -45,7 +46,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; */ class JsonMixinModuleTests { - private AnnotationConfigApplicationContext context; + private @Nullable AnnotationConfigApplicationContext context; @AfterEach void closeContext() { @@ -65,7 +66,7 @@ class JsonMixinModuleTests { @Test void jsonWithModuleWithRenameMixInClassShouldBeMixedIn() throws Exception { load(RenameMixInClass.class); - JsonMixinModule module = this.context.getBean(JsonMixinModule.class); + JsonMixinModule module = getContext().getBean(JsonMixinModule.class); assertMixIn(module, new Name("spring"), "{\"username\":\"spring\"}"); assertMixIn(module, NameAndAge.create("spring", 100), "{\"age\":100,\"username\":\"spring\"}"); } @@ -73,7 +74,7 @@ class JsonMixinModuleTests { @Test void jsonWithModuleWithEmptyMixInClassShouldNotBeMixedIn() throws Exception { load(EmptyMixInClass.class); - JsonMixinModule module = this.context.getBean(JsonMixinModule.class); + JsonMixinModule module = getContext().getBean(JsonMixinModule.class); assertMixIn(module, new Name("spring"), "{\"name\":\"spring\"}"); assertMixIn(module, NameAndAge.create("spring", 100), "{\"age\":100,\"name\":\"spring\"}"); } @@ -81,17 +82,23 @@ class JsonMixinModuleTests { @Test void jsonWithModuleWithRenameMixInAbstractClassShouldBeMixedIn() throws Exception { load(RenameMixInAbstractClass.class); - JsonMixinModule module = this.context.getBean(JsonMixinModule.class); + JsonMixinModule module = getContext().getBean(JsonMixinModule.class); assertMixIn(module, NameAndAge.create("spring", 100), "{\"age\":100,\"username\":\"spring\"}"); } @Test void jsonWithModuleWithRenameMixInInterfaceShouldBeMixedIn() throws Exception { load(RenameMixInInterface.class); - JsonMixinModule module = this.context.getBean(JsonMixinModule.class); + JsonMixinModule module = getContext().getBean(JsonMixinModule.class); assertMixIn(module, NameAndAge.create("spring", 100), "{\"age\":100,\"username\":\"spring\"}"); } + private AnnotationConfigApplicationContext getContext() { + AnnotationConfigApplicationContext context = this.context; + assertThat(context).isNotNull(); + return context; + } + private void load(Class... basePackageClasses) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.registerBean(JsonMixinModule.class, () -> createJsonMixinModule(context, basePackageClasses)); diff --git a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/NameAndAgeJsonComponent.java b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/NameAndAgeJsonComponent.java index f15673a24ad..382ef4c1f15 100644 --- a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/NameAndAgeJsonComponent.java +++ b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/NameAndAgeJsonComponent.java @@ -24,6 +24,8 @@ import tools.jackson.databind.SerializationContext; import org.springframework.boot.jackson.types.NameAndAge; +import static org.assertj.core.api.Assertions.assertThat; + /** * Sample {@link JsonComponent @JsonComponent} used for tests. * @@ -48,6 +50,7 @@ public class NameAndAgeJsonComponent { protected NameAndAge deserializeObject(JsonParser jsonParser, DeserializationContext context, JsonNode tree) { String name = nullSafeValue(tree.get("name"), String.class); Integer age = nullSafeValue(tree.get("age"), Integer.class); + assertThat(age).isNotNull(); return NameAndAge.create(name, age); } diff --git a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/ObjectValueDeserializerTests.java b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/ObjectValueDeserializerTests.java index 56c8bfb6fdd..c333127bcd0 100644 --- a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/ObjectValueDeserializerTests.java +++ b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/ObjectValueDeserializerTests.java @@ -22,6 +22,7 @@ import java.math.BigInteger; import java.time.LocalDate; import java.util.function.Function; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import tools.jackson.core.JsonParser; import tools.jackson.databind.DeserializationContext; @@ -193,19 +194,22 @@ class ObjectValueDeserializerTests { static class TestJsonObjectDeserializer extends ObjectValueDeserializer { @Override + @SuppressWarnings("unchecked") protected T deserializeObject(JsonParser jsonParser, DeserializationContext context, JsonNode tree) { - return null; + return (T) new Object(); } - R testNullSafeValue(JsonNode jsonNode, Class type, Function mapper) { + @Nullable R testNullSafeValue(JsonNode jsonNode, Class type, Function mapper) { return nullSafeValue(jsonNode, type, mapper); } - D testNullSafeValue(JsonNode jsonNode, Class type) { + @SuppressWarnings("NullAway") // Test null check + @Nullable D testNullSafeValue(@Nullable JsonNode jsonNode, @Nullable Class type) { return nullSafeValue(jsonNode, type); } - JsonNode testGetRequiredNode(JsonNode tree, String fieldName) { + @SuppressWarnings("NullAway") // Test null check + JsonNode testGetRequiredNode(@Nullable JsonNode tree, String fieldName) { return getRequiredNode(tree, fieldName); } diff --git a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/autoconfigure/JacksonAutoConfigurationTests.java b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/autoconfigure/JacksonAutoConfigurationTests.java index ab2c6baad69..e49ee40778a 100644 --- a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/autoconfigure/JacksonAutoConfigurationTests.java +++ b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/autoconfigure/JacksonAutoConfigurationTests.java @@ -26,6 +26,7 @@ import java.util.Set; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonInclude; import org.assertj.core.api.InstanceOfAssertFactories; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import tools.jackson.core.JsonGenerator; import tools.jackson.core.json.JsonReadFeature; @@ -624,9 +625,9 @@ class JacksonAutoConfigurationTests { @Configuration(proxyBeanMethods = false) static class JsonMapperBuilderConsumerConfig { - JsonMapper.Builder builderOne; + JsonMapper.@Nullable Builder builderOne; - JsonMapper.Builder builderTwo; + JsonMapper.@Nullable Builder builderTwo; @Bean String consumerOne(JsonMapper.Builder builder) { @@ -644,7 +645,7 @@ class JacksonAutoConfigurationTests { protected static final class Foo { - private String name; + private @Nullable String name; private Foo() { } @@ -653,11 +654,11 @@ class JacksonAutoConfigurationTests { return new Foo(); } - public String getName() { + public @Nullable String getName() { return this.name; } - public void setName(String name) { + public void setName(@Nullable String name) { this.name = name; } @@ -665,13 +666,13 @@ class JacksonAutoConfigurationTests { static class Bar { - private String propertyName; + private @Nullable String propertyName; - String getPropertyName() { + @Nullable String getPropertyName() { return this.propertyName; } - void setPropertyName(String propertyName) { + void setPropertyName(@Nullable String propertyName) { this.propertyName = propertyName; } @@ -708,11 +709,11 @@ class JacksonAutoConfigurationTests { @SuppressWarnings("unused") static class VisibilityBean { - private String property1; + private @Nullable String property1; - public String property2; + public @Nullable String property2; - String getProperty3() { + @Nullable String getProperty3() { return null; } @@ -721,13 +722,13 @@ class JacksonAutoConfigurationTests { static class Person { @JsonFormat(pattern = "yyyyMMdd") - private Date birthDate; + private @Nullable Date birthDate; - Date getBirthDate() { + @Nullable Date getBirthDate() { return this.birthDate; } - void setBirthDate(Date birthDate) { + void setBirthDate(@Nullable Date birthDate) { this.birthDate = birthDate; } diff --git a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/types/Name.java b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/types/Name.java index 1956f01e24d..1788d57e684 100644 --- a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/types/Name.java +++ b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/types/Name.java @@ -16,6 +16,8 @@ package org.springframework.boot.jackson.types; +import org.jspecify.annotations.Nullable; + /** * Sample object used for tests. * @@ -23,13 +25,13 @@ package org.springframework.boot.jackson.types; */ public class Name { - protected final String name; + protected final @Nullable String name; - public Name(String name) { + public Name(@Nullable String name) { this.name = name; } - public String getName() { + public @Nullable String getName() { return this.name; } diff --git a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/types/NameAndAge.java b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/types/NameAndAge.java index b429aba9a15..bb2b681a30c 100644 --- a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/types/NameAndAge.java +++ b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/types/NameAndAge.java @@ -16,6 +16,8 @@ package org.springframework.boot.jackson.types; +import org.jspecify.annotations.Nullable; + import org.springframework.util.ObjectUtils; /** @@ -28,7 +30,7 @@ public final class NameAndAge extends Name { private final int age; - private NameAndAge(String name, int age) { + private NameAndAge(@Nullable String name, int age) { super(name); this.age = age; } @@ -67,7 +69,7 @@ public final class NameAndAge extends Name { return result; } - public static NameAndAge create(String name, int age) { + public static NameAndAge create(@Nullable String name, int age) { return new NameAndAge(name, age); }