Browse Source

Add Jackson2Tester support

See gh-47688
pull/47694/head
Phillip Webb 2 months ago
parent
commit
50a73b8dfe
  1. 3
      core/spring-boot-test/build.gradle
  2. 207
      core/spring-boot-test/src/main/java/org/springframework/boot/test/json/Jackson2Tester.java
  3. 8
      core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JacksonTester.java
  4. 92
      core/spring-boot-test/src/test/java/org/springframework/boot/test/json/Jackson2TesterTests.java
  5. 63
      module/spring-boot-jackson2/src/main/java/org/springframework/boot/jackson2/autoconfigure/Jackson2TesterAutoConfiguration.java
  6. 1
      module/spring-boot-jackson2/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.json.AutoConfigureJson.imports
  7. 1
      module/spring-boot-jackson2/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters.imports
  8. 2
      module/spring-boot-jackson2/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.json.JsonTest.includes

3
core/spring-boot-test/build.gradle

@ -27,7 +27,7 @@ dependencies { @@ -27,7 +27,7 @@ dependencies {
api(project(":core:spring-boot"))
api("org.springframework:spring-test")
optional("tools.jackson.core:jackson-databind")
optional("com.fasterxml.jackson.core:jackson-databind")
optional("com.google.code.gson:gson")
optional("com.jayway.jsonpath:json-path")
optional("jakarta.json.bind:jakarta.json.bind-api")
@ -45,6 +45,7 @@ dependencies { @@ -45,6 +45,7 @@ dependencies {
optional("org.seleniumhq.selenium:selenium-api")
optional("org.skyscreamer:jsonassert")
optional("org.springframework:spring-web")
optional("tools.jackson.core:jackson-databind")
testImplementation(project(":test-support:spring-boot-test-support"))
testImplementation("ch.qos.logback:logback-classic")

207
core/spring-boot-test/src/main/java/org/springframework/boot/test/json/Jackson2Tester.java

@ -0,0 +1,207 @@ @@ -0,0 +1,207 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.test.json;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;
/**
* AssertJ based JSON tester backed by Jackson 2. Usually instantiated via
* {@link #initFields(Object, ObjectMapper)}, for example: <pre class="code">
* public class ExampleObjectJsonTests {
*
* private Jackson2Tester&lt;ExampleObject&gt; json;
*
* &#064;Before
* public void setup() {
* ObjectMapper objectMapper = new ObjectMapper();
* Jackson2Tester.initFields(this, objectMapper);
* }
*
* &#064;Test
* public void testWriteJson() throws IOException {
* ExampleObject object = //...
* assertThat(json.write(object)).isEqualToJson("expected.json");
* }
*
* }
* </pre>
*
* See {@link AbstractJsonMarshalTester} for more details.
*
* @param <T> the type under test
* @author Phillip Webb
* @author Madhura Bhave
* @author Diego Berrueta
* @since 4.0.0
* @deprecated since 4.0.0 for removal in 4.2.0 in favor of Jackson 3.
*/
@Deprecated(since = "4.0.0", forRemoval = true)
public class Jackson2Tester<T> extends AbstractJsonMarshalTester<T> {
private final ObjectMapper objectMapper;
private @Nullable Class<?> view;
/**
* Create a new {@link Jackson2Tester} instance.
* @param objectMapper the Jackson object mapper
*/
protected Jackson2Tester(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "'objectMapper' must not be null");
this.objectMapper = objectMapper;
}
/**
* Create a new {@link Jackson2Tester} instance.
* @param resourceLoadClass the source class used to load resources
* @param type the type under test
* @param objectMapper the Jackson object mapper
*/
public Jackson2Tester(Class<?> resourceLoadClass, ResolvableType type, ObjectMapper objectMapper) {
this(resourceLoadClass, type, objectMapper, null);
}
/**
* Create a new {@link Jackson2Tester} instance.
* @param resourceLoadClass the source class used to load resources
* @param type the type under test
* @param objectMapper the Jackson object mapper
* @param view the JSON view
*/
public Jackson2Tester(Class<?> resourceLoadClass, ResolvableType type, ObjectMapper objectMapper,
@Nullable Class<?> view) {
super(resourceLoadClass, type);
Assert.notNull(objectMapper, "'objectMapper' must not be null");
this.objectMapper = objectMapper;
this.view = view;
}
@Override
protected JsonContent<T> getJsonContent(String json) {
Configuration configuration = Configuration.builder()
.jsonProvider(new JacksonJsonProvider(this.objectMapper))
.mappingProvider(new JacksonMappingProvider(this.objectMapper))
.build();
Class<?> resourceLoadClass = getResourceLoadClass();
Assert.state(resourceLoadClass != null, "'resourceLoadClass' must not be null");
return new JsonContent<>(resourceLoadClass, getType(), json, configuration);
}
@Override
protected T readObject(InputStream inputStream, ResolvableType type) throws IOException {
return getObjectReader(type).readValue(inputStream);
}
@Override
protected T readObject(Reader reader, ResolvableType type) throws IOException {
return getObjectReader(type).readValue(reader);
}
private ObjectReader getObjectReader(ResolvableType type) {
ObjectReader objectReader = this.objectMapper.readerFor(getType(type));
if (this.view != null) {
return objectReader.withView(this.view);
}
return objectReader;
}
@Override
protected String writeObject(T value, ResolvableType type) throws IOException {
return getObjectWriter(type).writeValueAsString(value);
}
private ObjectWriter getObjectWriter(ResolvableType type) {
ObjectWriter objectWriter = this.objectMapper.writerFor(getType(type));
if (this.view != null) {
return objectWriter.withView(this.view);
}
return objectWriter;
}
private JavaType getType(ResolvableType type) {
return this.objectMapper.constructType(type.getType());
}
/**
* Utility method to initialize {@link Jackson2Tester} fields. See
* {@link Jackson2Tester class-level documentation} for example usage.
* @param testInstance the test instance
* @param objectMapper the JSON mapper
* @see #initFields(Object, ObjectMapper)
*/
public static void initFields(Object testInstance, ObjectMapper objectMapper) {
new Jackson2FieldInitializer().initFields(testInstance, objectMapper);
}
/**
* Utility method to initialize {@link Jackson2Tester} fields. See
* {@link Jackson2Tester class-level documentation} for example usage.
* @param testInstance the test instance
* @param objectMapperFactory a factory to create the object mapper
* @see #initFields(Object, ObjectMapper)
*/
public static void initFields(Object testInstance, ObjectFactory<ObjectMapper> objectMapperFactory) {
new Jackson2FieldInitializer().initFields(testInstance, objectMapperFactory);
}
/**
* Returns a new instance of {@link Jackson2Tester} with the view that should be used
* for json serialization/deserialization.
* @param view the view class
* @return the new instance
*/
public Jackson2Tester<T> forView(Class<?> view) {
Class<?> resourceLoadClass = getResourceLoadClass();
ResolvableType type = getType();
Assert.state(resourceLoadClass != null, "'resourceLoadClass' must not be null");
Assert.state(type != null, "'type' must not be null");
return new Jackson2Tester<>(resourceLoadClass, type, this.objectMapper, view);
}
/**
* {@link FieldInitializer} for Jackson.
*/
private static class Jackson2FieldInitializer extends FieldInitializer<ObjectMapper> {
protected Jackson2FieldInitializer() {
super(Jackson2Tester.class);
}
@Override
protected AbstractJsonMarshalTester<Object> createTester(Class<?> resourceLoadClass, ResolvableType type,
ObjectMapper marshaller) {
return new Jackson2Tester<>(resourceLoadClass, type, marshaller);
}
}
}

8
core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JacksonTester.java

@ -103,6 +103,14 @@ public class JacksonTester<T> extends AbstractJsonMarshalTester<T> { @@ -103,6 +103,14 @@ public class JacksonTester<T> extends AbstractJsonMarshalTester<T> {
this(resourceLoadClass, type, jsonMapper, null);
}
/**
* Create a new {@link JacksonTester} instance.
* @param resourceLoadClass the source class used to load resources
* @param type the type under test
* @param jsonMapper the Jackson JSON mapper
* @param view the JSON view
* @since 4.0.0
*/
public JacksonTester(Class<?> resourceLoadClass, ResolvableType type, JsonMapper jsonMapper,
@Nullable Class<?> view) {
super(resourceLoadClass, type);

92
core/spring-boot-test/src/test/java/org/springframework/boot/test/json/Jackson2TesterTests.java

@ -0,0 +1,92 @@ @@ -0,0 +1,92 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.test.json;
import java.util.List;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.core.ResolvableType;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link Jackson2Tester}.
*
* @author Phillip Webb
* @deprecated since 4.0.0 for removal in 4.2.0 in favor of JacksonTesterTests
*/
@Deprecated(since = "4.0.0", forRemoval = true)
@SuppressWarnings("removal")
class Jackson2TesterTests extends AbstractJsonMarshalTesterTests {
@Test
@SuppressWarnings("NullAway") // Test null check
void initFieldsWhenTestIsNullShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> Jackson2Tester.initFields(null, new ObjectMapper()))
.withMessageContaining("'testInstance' must not be null");
}
@Test
@SuppressWarnings("NullAway") // Test null check
void initFieldsWhenMarshallerIsNullShouldThrowException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> Jackson2Tester.initFields(new InitFieldsTestClass(), (ObjectMapper) null))
.withMessageContaining("'marshaller' must not be null");
}
@Test
void initFieldsShouldSetNullFields() {
InitFieldsTestClass test = new InitFieldsTestClass();
assertThat(test.test).isNull();
assertThat(test.base).isNull();
Jackson2Tester.initFields(test, new ObjectMapper());
assertThat(test.test).isNotNull();
assertThat(test.base).isNotNull();
ResolvableType type = test.test.getType();
assertThat(type).isNotNull();
assertThat(type.resolve()).isEqualTo(List.class);
assertThat(type.resolveGeneric()).isEqualTo(ExampleObject.class);
}
@Override
protected AbstractJsonMarshalTester<Object> createTester(Class<?> resourceLoadClass, ResolvableType type) {
return new org.springframework.boot.test.json.Jackson2Tester<>(resourceLoadClass, type, new ObjectMapper());
}
abstract static class InitFieldsBaseClass {
public org.springframework.boot.test.json.@Nullable Jackson2Tester<ExampleObject> base;
public org.springframework.boot.test.json.Jackson2Tester<ExampleObject> baseSet = new org.springframework.boot.test.json.Jackson2Tester<>(
InitFieldsBaseClass.class, ResolvableType.forClass(ExampleObject.class), new ObjectMapper());
}
static class InitFieldsTestClass extends InitFieldsBaseClass {
public org.springframework.boot.test.json.@Nullable Jackson2Tester<List<ExampleObject>> test;
public org.springframework.boot.test.json.Jackson2Tester<ExampleObject> testSet = new org.springframework.boot.test.json.Jackson2Tester<>(
InitFieldsBaseClass.class, ResolvableType.forClass(ExampleObject.class), new ObjectMapper());
}
}

63
module/spring-boot-jackson2/src/main/java/org/springframework/boot/jackson2/autoconfigure/Jackson2TesterAutoConfiguration.java

@ -0,0 +1,63 @@ @@ -0,0 +1,63 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jackson2.autoconfigure;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.test.autoconfigure.TestAutoConfiguration;
import org.springframework.boot.test.autoconfigure.json.ConditionalOnJsonTesters;
import org.springframework.boot.test.autoconfigure.json.JsonMarshalTesterRuntimeHints;
import org.springframework.boot.test.autoconfigure.json.JsonTesterFactoryBean;
import org.springframework.boot.test.json.GsonTester;
import org.springframework.boot.test.json.JacksonTester;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.context.annotation.Scope;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link GsonTester}.
*
* @author Phjllip Webb
* @deprecated since 4.0.0 for removal in 4.2.0 in favor of Jackson 3.
*/
@Deprecated(since = "4.0.0", forRemoval = true)
@TestAutoConfiguration(after = Jackson2AutoConfiguration.class)
@ConditionalOnJsonTesters
@SuppressWarnings("removal")
final class Jackson2TesterAutoConfiguration {
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@ConditionalOnBean(JsonMapper.class)
@ImportRuntimeHints(Jackson2TesterRuntimeHints.class)
FactoryBean<JacksonTester<?>> jacksonTesterFactoryBean(JsonMapper mapper) {
return new JsonTesterFactoryBean<>(JacksonTester.class, mapper);
}
static class Jackson2TesterRuntimeHints extends JsonMarshalTesterRuntimeHints {
Jackson2TesterRuntimeHints() {
super(JacksonTester.class);
}
}
}

1
module/spring-boot-jackson2/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.json.AutoConfigureJson.imports

@ -0,0 +1 @@ @@ -0,0 +1 @@
org.springframework.boot.jackson.autoconfigure.Jackson2AutoConfiguration

1
module/spring-boot-jackson2/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters.imports

@ -0,0 +1 @@ @@ -0,0 +1 @@
org.springframework.boot.jackson.autoconfigure.Jackson2TesterAutoConfiguration

2
module/spring-boot-jackson2/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.json.JsonTest.includes

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
org.springframework.boot.jackson2.JsonComponent
com.fasterxml.jackson.databind.Module
Loading…
Cancel
Save