Browse Source

Add nullability annotations to tests in module/spring-boot-jackson

See gh-47263
pull/47415/head
Moritz Halbritter 3 months ago
parent
commit
af941e0a9a
  1. 4
      module/spring-boot-jackson/build.gradle
  2. 30
      module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonComponentModuleTests.java
  3. 17
      module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonMixinModuleTests.java
  4. 3
      module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/NameAndAgeJsonComponent.java
  5. 12
      module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/ObjectValueDeserializerTests.java
  6. 29
      module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/autoconfigure/JacksonAutoConfigurationTests.java
  7. 8
      module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/types/Name.java
  8. 6
      module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/types/NameAndAge.java

4
module/spring-boot-jackson/build.gradle

@ -39,3 +39,7 @@ dependencies {
testImplementation(project(":test-support:spring-boot-test-support")) testImplementation(project(":test-support:spring-boot-test-support"))
testRuntimeOnly("ch.qos.logback:logback-classic") testRuntimeOnly("ch.qos.logback:logback-classic")
} }
tasks.named("compileTestJava") {
options.nullability.checking = "tests"
}

30
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.HashMap;
import java.util.Map; import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.core.JacksonException; 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.hint.predicate.RuntimeHintsPredicates;
import org.springframework.aot.test.generate.TestGenerationContext; import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.jackson.JsonComponentModule.JsonComponentBeanFactoryInitializationAotProcessor; import org.springframework.boot.jackson.JsonComponentModule.JsonComponentBeanFactoryInitializationAotProcessor;
import org.springframework.boot.jackson.JsonComponentModuleTests.ComponentWithInnerAbstractClass.AbstractSerializer; 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.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
/** /**
* Tests for {@link JsonComponentModule}. * Tests for {@link JsonComponentModule}.
@ -53,7 +56,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*/ */
class JsonComponentModuleTests { class JsonComponentModuleTests {
private AnnotationConfigApplicationContext context; private @Nullable AnnotationConfigApplicationContext context;
@AfterEach @AfterEach
void closeContext() { void closeContext() {
@ -65,21 +68,21 @@ class JsonComponentModuleTests {
@Test @Test
void moduleShouldRegisterSerializers() throws Exception { void moduleShouldRegisterSerializers() throws Exception {
load(OnlySerializer.class); load(OnlySerializer.class);
JsonComponentModule module = this.context.getBean(JsonComponentModule.class); JsonComponentModule module = getContext().getBean(JsonComponentModule.class);
assertSerialize(module); assertSerialize(module);
} }
@Test @Test
void moduleShouldRegisterDeserializers() throws Exception { void moduleShouldRegisterDeserializers() throws Exception {
load(OnlyDeserializer.class); load(OnlyDeserializer.class);
JsonComponentModule module = this.context.getBean(JsonComponentModule.class); JsonComponentModule module = getContext().getBean(JsonComponentModule.class);
assertDeserialize(module); assertDeserialize(module);
} }
@Test @Test
void moduleShouldRegisterInnerClasses() throws Exception { void moduleShouldRegisterInnerClasses() throws Exception {
load(NameAndAgeJsonComponent.class); load(NameAndAgeJsonComponent.class);
JsonComponentModule module = this.context.getBean(JsonComponentModule.class); JsonComponentModule module = getContext().getBean(JsonComponentModule.class);
assertSerialize(module); assertSerialize(module);
assertDeserialize(module); assertDeserialize(module);
} }
@ -96,21 +99,21 @@ class JsonComponentModuleTests {
@Test @Test
void moduleShouldRegisterKeySerializers() throws Exception { void moduleShouldRegisterKeySerializers() throws Exception {
load(OnlyKeySerializer.class); load(OnlyKeySerializer.class);
JsonComponentModule module = this.context.getBean(JsonComponentModule.class); JsonComponentModule module = getContext().getBean(JsonComponentModule.class);
assertKeySerialize(module); assertKeySerialize(module);
} }
@Test @Test
void moduleShouldRegisterKeyDeserializers() throws Exception { void moduleShouldRegisterKeyDeserializers() throws Exception {
load(OnlyKeyDeserializer.class); load(OnlyKeyDeserializer.class);
JsonComponentModule module = this.context.getBean(JsonComponentModule.class); JsonComponentModule module = getContext().getBean(JsonComponentModule.class);
assertKeyDeserialize(module); assertKeyDeserialize(module);
} }
@Test @Test
void moduleShouldRegisterInnerClassesForKeyHandlers() throws Exception { void moduleShouldRegisterInnerClassesForKeyHandlers() throws Exception {
load(NameAndAgeJsonKeyComponent.class); load(NameAndAgeJsonKeyComponent.class);
JsonComponentModule module = this.context.getBean(JsonComponentModule.class); JsonComponentModule module = getContext().getBean(JsonComponentModule.class);
assertKeySerialize(module); assertKeySerialize(module);
assertKeyDeserialize(module); assertKeyDeserialize(module);
} }
@ -118,7 +121,7 @@ class JsonComponentModuleTests {
@Test @Test
void moduleShouldRegisterOnlyForSpecifiedClasses() throws Exception { void moduleShouldRegisterOnlyForSpecifiedClasses() throws Exception {
load(NameAndCareerJsonComponent.class); 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, new NameAndCareer("spring", "developer"), "{\"name\":\"spring\"}");
assertSerialize(module, NameAndAge.create("spring", 100), "{\"age\":100,\"name\":\"spring\"}"); assertSerialize(module, NameAndAge.create("spring", 100), "{\"age\":100,\"name\":\"spring\"}");
assertDeserializeForSpecifiedClasses(module); assertDeserializeForSpecifiedClasses(module);
@ -127,11 +130,12 @@ class JsonComponentModuleTests {
@Test @Test
void aotContributionRegistersReflectionHintsForSuitableInnerClasses() { void aotContributionRegistersReflectionHintsForSuitableInnerClasses() {
load(ComponentWithInnerAbstractClass.class); load(ComponentWithInnerAbstractClass.class);
ConfigurableListableBeanFactory beanFactory = this.context.getBeanFactory(); ConfigurableListableBeanFactory beanFactory = getContext().getBeanFactory();
BeanFactoryInitializationAotContribution contribution = new JsonComponentBeanFactoryInitializationAotProcessor() BeanFactoryInitializationAotContribution contribution = new JsonComponentBeanFactoryInitializationAotProcessor()
.processAheadOfTime(beanFactory); .processAheadOfTime(beanFactory);
TestGenerationContext generationContext = new TestGenerationContext(); TestGenerationContext generationContext = new TestGenerationContext();
contribution.applyTo(generationContext, null); assertThat(contribution).isNotNull();
contribution.applyTo(generationContext, mock(BeanFactoryInitializationCode.class));
RuntimeHints runtimeHints = generationContext.getRuntimeHints(); RuntimeHints runtimeHints = generationContext.getRuntimeHints();
assertThat(RuntimeHintsPredicates.reflection().onType(ComponentWithInnerAbstractClass.class)) assertThat(RuntimeHintsPredicates.reflection().onType(ComponentWithInnerAbstractClass.class))
.accepts(runtimeHints); .accepts(runtimeHints);
@ -199,6 +203,12 @@ class JsonComponentModuleTests {
assertThat(map).containsEntry(NameAndAge.create("spring", 100), true); assertThat(map).containsEntry(NameAndAge.create("spring", 100), true);
} }
private AnnotationConfigApplicationContext getContext() {
AnnotationConfigApplicationContext context = this.context;
assertThat(context).isNotNull();
return context;
}
@JsonComponent @JsonComponent
static class OnlySerializer extends NameAndAgeJsonComponent.Serializer { static class OnlySerializer extends NameAndAgeJsonComponent.Serializer {

17
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.Arrays;
import java.util.List; import java.util.List;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.databind.JacksonModule; import tools.jackson.databind.JacksonModule;
@ -45,7 +46,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*/ */
class JsonMixinModuleTests { class JsonMixinModuleTests {
private AnnotationConfigApplicationContext context; private @Nullable AnnotationConfigApplicationContext context;
@AfterEach @AfterEach
void closeContext() { void closeContext() {
@ -65,7 +66,7 @@ class JsonMixinModuleTests {
@Test @Test
void jsonWithModuleWithRenameMixInClassShouldBeMixedIn() throws Exception { void jsonWithModuleWithRenameMixInClassShouldBeMixedIn() throws Exception {
load(RenameMixInClass.class); 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, new Name("spring"), "{\"username\":\"spring\"}");
assertMixIn(module, NameAndAge.create("spring", 100), "{\"age\":100,\"username\":\"spring\"}"); assertMixIn(module, NameAndAge.create("spring", 100), "{\"age\":100,\"username\":\"spring\"}");
} }
@ -73,7 +74,7 @@ class JsonMixinModuleTests {
@Test @Test
void jsonWithModuleWithEmptyMixInClassShouldNotBeMixedIn() throws Exception { void jsonWithModuleWithEmptyMixInClassShouldNotBeMixedIn() throws Exception {
load(EmptyMixInClass.class); 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, new Name("spring"), "{\"name\":\"spring\"}");
assertMixIn(module, NameAndAge.create("spring", 100), "{\"age\":100,\"name\":\"spring\"}"); assertMixIn(module, NameAndAge.create("spring", 100), "{\"age\":100,\"name\":\"spring\"}");
} }
@ -81,17 +82,23 @@ class JsonMixinModuleTests {
@Test @Test
void jsonWithModuleWithRenameMixInAbstractClassShouldBeMixedIn() throws Exception { void jsonWithModuleWithRenameMixInAbstractClassShouldBeMixedIn() throws Exception {
load(RenameMixInAbstractClass.class); 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\"}"); assertMixIn(module, NameAndAge.create("spring", 100), "{\"age\":100,\"username\":\"spring\"}");
} }
@Test @Test
void jsonWithModuleWithRenameMixInInterfaceShouldBeMixedIn() throws Exception { void jsonWithModuleWithRenameMixInInterfaceShouldBeMixedIn() throws Exception {
load(RenameMixInInterface.class); 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\"}"); 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) { private void load(Class<?>... basePackageClasses) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.registerBean(JsonMixinModule.class, () -> createJsonMixinModule(context, basePackageClasses)); context.registerBean(JsonMixinModule.class, () -> createJsonMixinModule(context, basePackageClasses));

3
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 org.springframework.boot.jackson.types.NameAndAge;
import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Sample {@link JsonComponent @JsonComponent} used for tests. * Sample {@link JsonComponent @JsonComponent} used for tests.
* *
@ -48,6 +50,7 @@ public class NameAndAgeJsonComponent {
protected NameAndAge deserializeObject(JsonParser jsonParser, DeserializationContext context, JsonNode tree) { protected NameAndAge deserializeObject(JsonParser jsonParser, DeserializationContext context, JsonNode tree) {
String name = nullSafeValue(tree.get("name"), String.class); String name = nullSafeValue(tree.get("name"), String.class);
Integer age = nullSafeValue(tree.get("age"), Integer.class); Integer age = nullSafeValue(tree.get("age"), Integer.class);
assertThat(age).isNotNull();
return NameAndAge.create(name, age); return NameAndAge.create(name, age);
} }

12
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.time.LocalDate;
import java.util.function.Function; import java.util.function.Function;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.core.JsonParser; import tools.jackson.core.JsonParser;
import tools.jackson.databind.DeserializationContext; import tools.jackson.databind.DeserializationContext;
@ -193,19 +194,22 @@ class ObjectValueDeserializerTests {
static class TestJsonObjectDeserializer<T> extends ObjectValueDeserializer<T> { static class TestJsonObjectDeserializer<T> extends ObjectValueDeserializer<T> {
@Override @Override
@SuppressWarnings("unchecked")
protected T deserializeObject(JsonParser jsonParser, DeserializationContext context, JsonNode tree) { protected T deserializeObject(JsonParser jsonParser, DeserializationContext context, JsonNode tree) {
return null; return (T) new Object();
} }
<D, R> R testNullSafeValue(JsonNode jsonNode, Class<D> type, Function<D, R> mapper) { <D, R> @Nullable R testNullSafeValue(JsonNode jsonNode, Class<D> type, Function<D, R> mapper) {
return nullSafeValue(jsonNode, type, mapper); return nullSafeValue(jsonNode, type, mapper);
} }
<D> D testNullSafeValue(JsonNode jsonNode, Class<D> type) { @SuppressWarnings("NullAway") // Test null check
<D> @Nullable D testNullSafeValue(@Nullable JsonNode jsonNode, @Nullable Class<D> type) {
return nullSafeValue(jsonNode, 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); return getRequiredNode(tree, fieldName);
} }

29
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.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import org.assertj.core.api.InstanceOfAssertFactories; import org.assertj.core.api.InstanceOfAssertFactories;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.core.JsonGenerator; import tools.jackson.core.JsonGenerator;
import tools.jackson.core.json.JsonReadFeature; import tools.jackson.core.json.JsonReadFeature;
@ -624,9 +625,9 @@ class JacksonAutoConfigurationTests {
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
static class JsonMapperBuilderConsumerConfig { static class JsonMapperBuilderConsumerConfig {
JsonMapper.Builder builderOne; JsonMapper.@Nullable Builder builderOne;
JsonMapper.Builder builderTwo; JsonMapper.@Nullable Builder builderTwo;
@Bean @Bean
String consumerOne(JsonMapper.Builder builder) { String consumerOne(JsonMapper.Builder builder) {
@ -644,7 +645,7 @@ class JacksonAutoConfigurationTests {
protected static final class Foo { protected static final class Foo {
private String name; private @Nullable String name;
private Foo() { private Foo() {
} }
@ -653,11 +654,11 @@ class JacksonAutoConfigurationTests {
return new Foo(); return new Foo();
} }
public String getName() { public @Nullable String getName() {
return this.name; return this.name;
} }
public void setName(String name) { public void setName(@Nullable String name) {
this.name = name; this.name = name;
} }
@ -665,13 +666,13 @@ class JacksonAutoConfigurationTests {
static class Bar { static class Bar {
private String propertyName; private @Nullable String propertyName;
String getPropertyName() { @Nullable String getPropertyName() {
return this.propertyName; return this.propertyName;
} }
void setPropertyName(String propertyName) { void setPropertyName(@Nullable String propertyName) {
this.propertyName = propertyName; this.propertyName = propertyName;
} }
@ -708,11 +709,11 @@ class JacksonAutoConfigurationTests {
@SuppressWarnings("unused") @SuppressWarnings("unused")
static class VisibilityBean { static class VisibilityBean {
private String property1; private @Nullable String property1;
public String property2; public @Nullable String property2;
String getProperty3() { @Nullable String getProperty3() {
return null; return null;
} }
@ -721,13 +722,13 @@ class JacksonAutoConfigurationTests {
static class Person { static class Person {
@JsonFormat(pattern = "yyyyMMdd") @JsonFormat(pattern = "yyyyMMdd")
private Date birthDate; private @Nullable Date birthDate;
Date getBirthDate() { @Nullable Date getBirthDate() {
return this.birthDate; return this.birthDate;
} }
void setBirthDate(Date birthDate) { void setBirthDate(@Nullable Date birthDate) {
this.birthDate = birthDate; this.birthDate = birthDate;
} }

8
module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/types/Name.java

@ -16,6 +16,8 @@
package org.springframework.boot.jackson.types; package org.springframework.boot.jackson.types;
import org.jspecify.annotations.Nullable;
/** /**
* Sample object used for tests. * Sample object used for tests.
* *
@ -23,13 +25,13 @@ package org.springframework.boot.jackson.types;
*/ */
public class Name { public class Name {
protected final String name; protected final @Nullable String name;
public Name(String name) { public Name(@Nullable String name) {
this.name = name; this.name = name;
} }
public String getName() { public @Nullable String getName() {
return this.name; return this.name;
} }

6
module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/types/NameAndAge.java

@ -16,6 +16,8 @@
package org.springframework.boot.jackson.types; package org.springframework.boot.jackson.types;
import org.jspecify.annotations.Nullable;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
/** /**
@ -28,7 +30,7 @@ public final class NameAndAge extends Name {
private final int age; private final int age;
private NameAndAge(String name, int age) { private NameAndAge(@Nullable String name, int age) {
super(name); super(name);
this.age = age; this.age = age;
} }
@ -67,7 +69,7 @@ public final class NameAndAge extends Name {
return result; return result;
} }
public static NameAndAge create(String name, int age) { public static NameAndAge create(@Nullable String name, int age) {
return new NameAndAge(name, age); return new NameAndAge(name, age);
} }

Loading…
Cancel
Save