Allow JSON Testers to be `@Autowired`

Switch `@AutoConfigureJsonTesters` to use regular `@Autowired` injection
for JSON testers. Prior to this commit JSON Tester fields were
initialized directly which caused IDE issues and was also a little
confusing.

Fixes gh-6451
This commit is contained in:
Phillip Webb 2016-07-26 19:44:47 -07:00
parent a44cc196de
commit 296dc7132b
13 changed files with 289 additions and 377 deletions

View File

@ -5011,12 +5011,13 @@ annotation.
Spring Boot includes AssertJ based helpers that work with the JSONassert and JsonPath Spring Boot includes AssertJ based helpers that work with the JSONassert and JsonPath
libraries to check that JSON is as expected. The `JacksonHelper`, `GsonHelper` and libraries to check that JSON is as expected. The `JacksonHelper`, `GsonHelper` and
`BasicJsonTester` classes can be used for Jackson, Gson and Strings respectively. Any `BasicJsonTester` classes can be used for Jackson, Gson and Strings respectively. Any
helper fields on the test class will be automatically initialized when using `@JsonTest`. helper fields on the test class can be `@Autowired` when using `@JsonTest`.
[source,java,indent=0] [source,java,indent=0]
---- ----
import org.junit.*; import org.junit.*;
import org.junit.runner.*; import org.junit.runner.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.boot.test.autoconfigure.json.*; import org.springframework.boot.test.autoconfigure.json.*;
import org.springframework.boot.test.context.*; import org.springframework.boot.test.context.*;
import org.springframework.boot.test.json.*; import org.springframework.boot.test.json.*;
@ -5028,6 +5029,7 @@ helper fields on the test class will be automatically initialized when using `@J
@JsonTest @JsonTest
public class MyJsonTests { public class MyJsonTests {
@Autowired
private JacksonTester<VehicleDetails> json; private JacksonTester<VehicleDetails> json;
@Test @Test

View File

@ -19,6 +19,7 @@ package sample.test.service;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.JsonTest; import org.springframework.boot.test.autoconfigure.json.JsonTest;
import org.springframework.boot.test.json.JacksonTester; import org.springframework.boot.test.json.JacksonTester;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
@ -34,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat;
@JsonTest @JsonTest
public class VehicleDetailsJsonTests { public class VehicleDetailsJsonTests {
@Autowired
private JacksonTester<VehicleDetails> json; private JacksonTester<VehicleDetails> json;
@Test @Test

View File

@ -18,42 +18,34 @@ package org.springframework.boot.test.autoconfigure.json;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.test.autoconfigure.properties.PropertyMapping;
import org.springframework.boot.test.json.BasicJsonTester; import org.springframework.boot.test.json.BasicJsonTester;
import org.springframework.boot.test.json.GsonTester; import org.springframework.boot.test.json.GsonTester;
import org.springframework.boot.test.json.JacksonTester; import org.springframework.boot.test.json.JacksonTester;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.TestExecutionListeners.MergeMode;
/** /**
* Annotation that can be applied to a test class to enable and configure * Annotation that can be applied to a test class to enable and configure
* auto-configuration of JSON testers. * auto-configuration of JSON testers.
* <p>
* NOTE: {@code @AutoConfigureJsonTesters} works in conjunction with
* {@link JsonTesterInitializationTestExecutionListener}. If you declare your own
* {@link TestExecutionListeners @TestExecutionListeners} and don't
* {@link MergeMode#MERGE_WITH_DEFAULTS merge with defaults} you must include
* {@link JsonTesterInitializationTestExecutionListener} to use this annotation.
* *
* @author Phillip Webb * @author Phillip Webb
* @see JsonTesterInitializationTestExecutionListener
*/ */
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@Inherited @ImportAutoConfiguration
@PropertyMapping("spring.test.jsontesters")
public @interface AutoConfigureJsonTesters { public @interface AutoConfigureJsonTesters {
/** /**
* If {@link BasicJsonTester}, {@link JacksonTester} and {@link GsonTester} fields * If {@link BasicJsonTester}, {@link JacksonTester} and {@link GsonTester} beans
* should be initialized using marshallers from the {@link ApplicationContext}. * should be registered. Defaults to {@code true}
* @return if JSON tester fields should be initialized * @return if tester support is enabled
*/ */
boolean initFields() default true; boolean enabled() default true;
} }

View File

@ -1,174 +0,0 @@
/*
* Copyright 2012-2016 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
*
* http://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.autoconfigure.json;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.test.json.BasicJsonTester;
import org.springframework.boot.test.json.GsonTester;
import org.springframework.boot.test.json.JacksonTester;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestExecutionListener;
import org.springframework.test.context.support.AbstractTestExecutionListener;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;
/**
* {@link TestExecutionListener} to initialize JSON tester fields.
*
* @author Phillip Webb
* @since 1.4.0
*/
public class JsonTesterInitializationTestExecutionListener
extends AbstractTestExecutionListener {
private static final String ASSERTJ_CLASS = "org.assertj.core.api.Assert";
private static final Map<String, Class<?>> INITIALIZERS;
static {
Map<String, Class<?>> initializers = new LinkedHashMap<String, Class<?>>();
initializers.put("com.fasterxml.jackson.databind.ObjectMapper",
JacksonInitializer.class);
initializers.put("com.google.gson.Gson", GsonInitializer.class);
INITIALIZERS = Collections.unmodifiableMap(initializers);
}
@Override
public void prepareTestInstance(TestContext testContext) throws Exception {
ClassLoader classLoader = getClass().getClassLoader();
if (ClassUtils.isPresent(ASSERTJ_CLASS, classLoader)
&& shouldInitializeFields(testContext)) {
initializeBasicJsonTesterFields(testContext);
initializeJsonMarshalTesterFields(classLoader, testContext);
}
}
private boolean shouldInitializeFields(TestContext testContext) {
AutoConfigureJsonTesters annotation = AnnotatedElementUtils.getMergedAnnotation(
testContext.getTestClass(), AutoConfigureJsonTesters.class);
return (annotation != null && annotation.initFields());
}
private void initializeBasicJsonTesterFields(final TestContext testContext) {
ReflectionUtils.doWithFields(testContext.getTestClass(), new FieldCallback() {
@Override
public void doWith(Field field)
throws IllegalArgumentException, IllegalAccessException {
if (BasicJsonTester.class.isAssignableFrom(field.getType())) {
setupField(field);
}
}
private void setupField(Field field) {
ReflectionUtils.makeAccessible(field);
Object existingInstance = ReflectionUtils.getField(field,
testContext.getTestInstance());
if (existingInstance == null) {
ReflectionUtils.setField(field, testContext.getTestInstance(),
new BasicJsonTester(testContext.getTestClass()));
}
}
});
}
private void initializeJsonMarshalTesterFields(ClassLoader classLoader,
TestContext testContext) {
for (Map.Entry<String, Class<?>> entry : INITIALIZERS.entrySet()) {
if (ClassUtils.isPresent(entry.getKey(), classLoader)) {
initializeJsonMarshalTesterFields(classLoader, testContext,
entry.getKey(), entry.getValue());
}
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void initializeJsonMarshalTesterFields(ClassLoader classLoader,
TestContext testContext, String marshallerClassName, Class<?> initializer) {
try {
Constructor<?> constructor = initializer.getDeclaredConstructor();
ReflectionUtils.makeAccessible(constructor);
initializeJsonMarshalTesterFields(testContext,
ClassUtils.resolveClassName(marshallerClassName, classLoader),
(Initializer) constructor.newInstance());
}
catch (Throwable ex) {
throw new IllegalStateException(ex);
}
}
private <T> void initializeJsonMarshalTesterFields(final TestContext testContext,
final Class<T> marshallerClass, Initializer<T> initializer) {
initializer.initialize(testContext, new ObjectFactory<T>() {
@Override
public T getObject() throws BeansException {
return testContext.getApplicationContext().getBean(marshallerClass);
}
});
}
/**
* Strategy used to initialize JSON testers without cause class not found exceptions.
* @param <M> the marshaller type
*/
interface Initializer<M> {
void initialize(TestContext testContext, ObjectFactory<M> marshaller);
}
/**
* {@link Initializer} for {@link JacksonTester}.
*/
static class JacksonInitializer implements Initializer<ObjectMapper> {
@Override
public void initialize(TestContext testContext,
ObjectFactory<ObjectMapper> marshaller) {
JacksonTester.initFields(testContext.getTestInstance(), marshaller);
}
}
/**
* {@link Initializer} for {@link GsonTester}.
*/
static class GsonInitializer implements Initializer<Gson> {
@Override
public void initialize(TestContext testContext, ObjectFactory<Gson> marshaller) {
GsonTester.initFields(testContext.getTestInstance(), marshaller);
}
}
}

View File

@ -0,0 +1,171 @@
/*
* Copyright 2012-2016 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
*
* http://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.autoconfigure.json;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.test.json.AbstractJsonMarshalTester;
import org.springframework.boot.test.json.BasicJsonTester;
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.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.core.ResolvableType;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;
/**
* Auto-configuration for Json testers.
*
* @author Phillip Webb
* @see AutoConfigureJsonTesters
* @since 1.4.0
*/
@Configuration
@ConditionalOnClass(name = "org.assertj.core.api.Assert")
@ConditionalOnProperty("spring.test.jsontesters.enabled")
public class JsonTestersAutoConfiguration {
@Bean
public static JsonMarshalTestersBeanPostProcessor jsonMarshalTestersBeanPostProcessor() {
return new JsonMarshalTestersBeanPostProcessor();
}
@Bean
@Scope("prototype")
public FactoryBean<BasicJsonTester> BasicJsonTesterFactoryBean() {
return new JsonTesterFactoryBean<BasicJsonTester, Void>(BasicJsonTester.class,
null);
}
@Bean
@Scope("prototype")
@ConditionalOnClass(ObjectMapper.class)
@ConditionalOnBean(ObjectMapper.class)
public FactoryBean<JacksonTester<?>> jacksonTesterFactoryBean(ObjectMapper mapper) {
return new JsonTesterFactoryBean<JacksonTester<?>, ObjectMapper>(
JacksonTester.class, mapper);
}
@Bean
@Scope("prototype")
@ConditionalOnClass(ObjectMapper.class)
@ConditionalOnBean(Gson.class)
public FactoryBean<GsonTester<?>> gsonTesterFactoryBean(Gson gson) {
return new JsonTesterFactoryBean<GsonTester<?>, Gson>(GsonTester.class, gson);
}
/**
* {@link FactoryBean} used to create JSON Tester instances.
*/
private class JsonTesterFactoryBean<T, M> implements FactoryBean<T> {
private final Class<?> objectType;
private final M marshaller;
JsonTesterFactoryBean(Class<?> objectType, M marshaller) {
this.objectType = objectType;
this.marshaller = marshaller;
}
@Override
public boolean isSingleton() {
return false;
}
@Override
@SuppressWarnings("unchecked")
public T getObject() throws Exception {
if (this.marshaller == null) {
Constructor<?> constructor = this.objectType.getDeclaredConstructor();
ReflectionUtils.makeAccessible(constructor);
return (T) BeanUtils.instantiateClass(constructor);
}
Constructor<?>[] constructors = this.objectType.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
if (constructor.getParameterTypes().length == 1
&& constructor.getParameterTypes()[0]
.isInstance(this.marshaller)) {
ReflectionUtils.makeAccessible(constructor);
return (T) BeanUtils.instantiateClass(constructor, this.marshaller);
}
}
throw new IllegalStateException(
this.objectType + " does not have a usable constructor");
}
@Override
public Class<?> getObjectType() {
return this.objectType;
}
}
/**
* {@link BeanPostProcessor} used to initialize JSON testers.
*/
private static class JsonMarshalTestersBeanPostProcessor
extends InstantiationAwareBeanPostProcessorAdapter {
@Override
public Object postProcessAfterInitialization(final Object bean, String beanName)
throws BeansException {
ReflectionUtils.doWithFields(bean.getClass(), new FieldCallback() {
@Override
public void doWith(Field field)
throws IllegalArgumentException, IllegalAccessException {
processFiled(bean, field);
}
});
return bean;
}
private void processFiled(Object bean, Field field) {
if (AbstractJsonMarshalTester.class.isAssignableFrom(field.getType())
|| BasicJsonTester.class.isAssignableFrom(field.getType())) {
ResolvableType type = ResolvableType.forField(field).getGeneric();
ReflectionUtils.makeAccessible(field);
Object tester = ReflectionUtils.getField(field, bean);
if (tester != null) {
ReflectionTestUtils.invokeMethod(tester, "initialize",
bean.getClass(), type);
}
}
}
}
}

View File

@ -17,6 +17,10 @@ org.springframework.boot.test.autoconfigure.json.AutoConfigureJson=\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\ org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
# AutoConfigureJsonTesters auto-configuration imports
org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters=\
org.springframework.boot.test.autoconfigure.json.JsonTestersAutoConfiguration
# AutoConfigureMockMvc auto-configuration imports # AutoConfigureMockMvc auto-configuration imports
org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc=\ org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc=\
org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration,\ org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration,\
@ -69,6 +73,5 @@ org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCus
# Test Execution Listeners # Test Execution Listeners
org.springframework.test.context.TestExecutionListener=\ org.springframework.test.context.TestExecutionListener=\
org.springframework.boot.test.autoconfigure.AutoConfigureReportTestExecutionListener,\ org.springframework.boot.test.autoconfigure.AutoConfigureReportTestExecutionListener,\
org.springframework.boot.test.autoconfigure.json.JsonTesterInitializationTestExecutionListener,\
org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener,\ org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener,\
org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener

View File

@ -19,6 +19,7 @@ package org.springframework.boot.test.autoconfigure.json;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.json.BasicJsonTester; import org.springframework.boot.test.json.BasicJsonTester;
import org.springframework.boot.test.json.GsonTester; import org.springframework.boot.test.json.GsonTester;
import org.springframework.boot.test.json.JacksonTester; import org.springframework.boot.test.json.JacksonTester;
@ -35,12 +36,16 @@ import static org.assertj.core.api.Assertions.assertThat;
@JsonTest @JsonTest
public class JsonTestIntegrationTests { public class JsonTestIntegrationTests {
@Autowired
private BasicJsonTester basicJson; private BasicJsonTester basicJson;
@Autowired
private JacksonTester<ExampleBasicObject> jacksonBasicJson; private JacksonTester<ExampleBasicObject> jacksonBasicJson;
@Autowired
private JacksonTester<ExampleCustomObject> jacksonCustomJson; private JacksonTester<ExampleCustomObject> jacksonCustomJson;
@Autowired
private GsonTester<ExampleBasicObject> gsonJson; private GsonTester<ExampleBasicObject> gsonJson;
@Test @Test

View File

@ -19,6 +19,7 @@ package org.springframework.boot.test.autoconfigure.json;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.json.BasicJsonTester; import org.springframework.boot.test.json.BasicJsonTester;
import org.springframework.boot.test.json.GsonTester; import org.springframework.boot.test.json.GsonTester;
import org.springframework.boot.test.json.JacksonTester; import org.springframework.boot.test.json.JacksonTester;
@ -33,13 +34,16 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@JsonTest @JsonTest
@AutoConfigureJsonTesters(initFields = false) @AutoConfigureJsonTesters(enabled = false)
public class JsonTestWithAutoConfigureJsonTestersTests { public class JsonTestWithAutoConfigureJsonTestersTests {
@Autowired(required = false)
private BasicJsonTester basicJson; private BasicJsonTester basicJson;
@Autowired(required = false)
private JacksonTester<ExampleBasicObject> jacksonTester; private JacksonTester<ExampleBasicObject> jacksonTester;
@Autowired(required = false)
private GsonTester<ExampleBasicObject> gsonTester; private GsonTester<ExampleBasicObject> gsonTester;
@Test @Test

View File

@ -1,179 +0,0 @@
/*
* Copyright 2012-2016 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
*
* http://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.autoconfigure.json;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.GsonBuilder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.test.json.BasicJsonTester;
import org.springframework.boot.test.json.GsonTester;
import org.springframework.boot.test.json.JacksonTester;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.test.context.TestContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link JsonTesterInitializationTestExecutionListener}.
*
* @author Phillip Webb
*/
public class JsonTesterInitializationTestExecutionListenerTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private JsonTesterInitializationTestExecutionListener listener = new JsonTesterInitializationTestExecutionListener();
@Test
public void prepareTestContextShouldInitializeBasicJsonTester() throws Exception {
WithBasicJsonTester instance = new WithBasicJsonTester();
this.listener.prepareTestInstance(mockTestContext(instance));
assertThat(instance.tester).isNotNull();
}
@Test
public void prepareTestContextShouldInitializeJacksonTester() throws Exception {
WithJacksonTester instance = new WithJacksonTester();
this.listener.prepareTestInstance(mockTestContext(instance, new ObjectMapper()));
assertThat(instance.tester).isNotNull();
}
@Test
public void prepareTestContextShouldInitializeGsonTester() throws Exception {
WithGsonTester instance = new WithGsonTester();
this.listener.prepareTestInstance(
mockTestContext(instance, new GsonBuilder().create()));
assertThat(instance.tester).isNotNull();
}
@Test
public void prepareTestContextWhenInitFieldsFalseShouldNotInitializeTesters()
throws Exception {
WithInitFieldsFalse instance = new WithInitFieldsFalse();
this.listener.prepareTestInstance(mockTestContext(instance, new ObjectMapper()));
assertThat(instance.basicTester).isNull();
assertThat(instance.jacksonTester).isNull();
assertThat(instance.gsonTester).isNull();
}
@Test
public void prepareTestContextWhenInitFieldsTrueShouldInitializeTesters()
throws Exception {
WithInitFieldsTrue instance = new WithInitFieldsTrue();
this.listener.prepareTestInstance(mockTestContext(instance));
assertThat(instance.tester).isNotNull();
}
@Test
public void prepareTestContextWhenMissingAnnotationShouldNotInitializeTesters()
throws Exception {
WithoutAnnotation instance = new WithoutAnnotation();
this.listener.prepareTestInstance(mockTestContext(instance));
assertThat(instance.basicTester).isNull();
assertThat(instance.jacksonTester).isNull();
assertThat(instance.gsonTester).isNull();
}
@Test
public void prepareTestContextWhenHasJacksonTesterButNoObjectMapperBeanShouldThrowException()
throws Exception {
WithJacksonTester instance = new WithJacksonTester();
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("ObjectMapper");
this.listener.prepareTestInstance(mockTestContext(instance));
}
@Test
public void prepareTestContextWhenHasJacksonTesterButNoGsonBeanShouldThrowException()
throws Exception {
WithGsonTester instance = new WithGsonTester();
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Gson");
this.listener.prepareTestInstance(mockTestContext(instance));
}
private TestContext mockTestContext(Object testInstance) {
return mockTestContext(testInstance, null);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private TestContext mockTestContext(Object testInstance, Object bean) {
TestContext testContext = mock(TestContext.class);
StaticApplicationContext applicationContext = new StaticApplicationContext();
if (bean != null) {
applicationContext.getBeanFactory().registerSingleton("bean", bean);
}
given(testContext.getApplicationContext()).willReturn(applicationContext);
given(testContext.getTestClass()).willReturn((Class) testInstance.getClass());
given(testContext.getTestInstance()).willReturn(testInstance);
return testContext;
}
@AutoConfigureJsonTesters
static class WithBasicJsonTester {
private BasicJsonTester tester;
}
@AutoConfigureJsonTesters
static class WithJacksonTester {
private JacksonTester<Object> tester;
}
@AutoConfigureJsonTesters
static class WithGsonTester {
private GsonTester<Object> tester;
}
@AutoConfigureJsonTesters(initFields = false)
static class WithInitFieldsFalse {
private BasicJsonTester basicTester;
private JacksonTester<Object> jacksonTester;
private GsonTester<Object> gsonTester;
}
@AutoConfigureJsonTesters(initFields = true)
static class WithInitFieldsTrue {
private BasicJsonTester tester;
}
static class WithoutAnnotation {
private BasicJsonTester basicTester;
private JacksonTester<Object> jacksonTester;
private GsonTester<Object> gsonTester;
}
}

View File

@ -70,9 +70,15 @@ import org.springframework.util.ReflectionUtils.FieldCallback;
*/ */
public abstract class AbstractJsonMarshalTester<T> { public abstract class AbstractJsonMarshalTester<T> {
private final Class<?> resourceLoadClass; private Class<?> resourceLoadClass;
private final ResolvableType type; private ResolvableType type;
/**
* Create a new uninitialized {@link AbstractJsonMarshalTester} instance.
*/
protected AbstractJsonMarshalTester() {
}
/** /**
* Create a new {@link AbstractJsonMarshalTester} instance. * Create a new {@link AbstractJsonMarshalTester} instance.
@ -83,9 +89,21 @@ public abstract class AbstractJsonMarshalTester<T> {
public AbstractJsonMarshalTester(Class<?> resourceLoadClass, ResolvableType type) { public AbstractJsonMarshalTester(Class<?> resourceLoadClass, ResolvableType type) {
Assert.notNull(resourceLoadClass, "ResourceLoadClass must not be null"); Assert.notNull(resourceLoadClass, "ResourceLoadClass must not be null");
Assert.notNull(type, "Type must not be null"); Assert.notNull(type, "Type must not be null");
initialize(resourceLoadClass, type);
}
/**
* Initialize the marshal tester for use.
* @param resourceLoadClass the source class used when loading relative classpath
* resources
* @param type the type under test
*/
protected final void initialize(Class<?> resourceLoadClass, ResolvableType type) {
if (this.resourceLoadClass == null && this.type == null) {
this.resourceLoadClass = resourceLoadClass; this.resourceLoadClass = resourceLoadClass;
this.type = type; this.type = type;
} }
}
/** /**
* Return the type under test. * Return the type under test.
@ -102,6 +120,7 @@ public abstract class AbstractJsonMarshalTester<T> {
* @throws IOException on write error * @throws IOException on write error
*/ */
public JsonContent<T> write(T value) throws IOException { public JsonContent<T> write(T value) throws IOException {
verify();
Assert.notNull(value, "Value must not be null"); Assert.notNull(value, "Value must not be null");
String json = writeObject(value, this.type); String json = writeObject(value, this.type);
return new JsonContent<T>(this.resourceLoadClass, this.type, json); return new JsonContent<T>(this.resourceLoadClass, this.type, json);
@ -114,6 +133,7 @@ public abstract class AbstractJsonMarshalTester<T> {
* @throws IOException on parse error * @throws IOException on parse error
*/ */
public T parseObject(byte[] jsonBytes) throws IOException { public T parseObject(byte[] jsonBytes) throws IOException {
verify();
return parse(jsonBytes).getObject(); return parse(jsonBytes).getObject();
} }
@ -124,6 +144,7 @@ public abstract class AbstractJsonMarshalTester<T> {
* @throws IOException on parse error * @throws IOException on parse error
*/ */
public ObjectContent<T> parse(byte[] jsonBytes) throws IOException { public ObjectContent<T> parse(byte[] jsonBytes) throws IOException {
verify();
Assert.notNull(jsonBytes, "JsonBytes must not be null"); Assert.notNull(jsonBytes, "JsonBytes must not be null");
return read(new ByteArrayResource(jsonBytes)); return read(new ByteArrayResource(jsonBytes));
} }
@ -135,6 +156,7 @@ public abstract class AbstractJsonMarshalTester<T> {
* @throws IOException on parse error * @throws IOException on parse error
*/ */
public T parseObject(String jsonString) throws IOException { public T parseObject(String jsonString) throws IOException {
verify();
return parse(jsonString).getObject(); return parse(jsonString).getObject();
} }
@ -145,6 +167,7 @@ public abstract class AbstractJsonMarshalTester<T> {
* @throws IOException on parse error * @throws IOException on parse error
*/ */
public ObjectContent<T> parse(String jsonString) throws IOException { public ObjectContent<T> parse(String jsonString) throws IOException {
verify();
Assert.notNull(jsonString, "JsonString must not be null"); Assert.notNull(jsonString, "JsonString must not be null");
return read(new StringReader(jsonString)); return read(new StringReader(jsonString));
} }
@ -157,6 +180,7 @@ public abstract class AbstractJsonMarshalTester<T> {
* @throws IOException on read error * @throws IOException on read error
*/ */
public T readObject(String resourcePath) throws IOException { public T readObject(String resourcePath) throws IOException {
verify();
return read(resourcePath).getObject(); return read(resourcePath).getObject();
} }
@ -168,6 +192,7 @@ public abstract class AbstractJsonMarshalTester<T> {
* @throws IOException on read error * @throws IOException on read error
*/ */
public ObjectContent<T> read(String resourcePath) throws IOException { public ObjectContent<T> read(String resourcePath) throws IOException {
verify();
Assert.notNull(resourcePath, "ResourcePath must not be null"); Assert.notNull(resourcePath, "ResourcePath must not be null");
return read(new ClassPathResource(resourcePath, this.resourceLoadClass)); return read(new ClassPathResource(resourcePath, this.resourceLoadClass));
} }
@ -179,6 +204,7 @@ public abstract class AbstractJsonMarshalTester<T> {
* @throws IOException on read error * @throws IOException on read error
*/ */
public T readObject(File file) throws IOException { public T readObject(File file) throws IOException {
verify();
return read(file).getObject(); return read(file).getObject();
} }
@ -189,6 +215,7 @@ public abstract class AbstractJsonMarshalTester<T> {
* @throws IOException on read error * @throws IOException on read error
*/ */
public ObjectContent<T> read(File file) throws IOException { public ObjectContent<T> read(File file) throws IOException {
verify();
Assert.notNull(file, "File must not be null"); Assert.notNull(file, "File must not be null");
return read(new FileSystemResource(file)); return read(new FileSystemResource(file));
} }
@ -200,6 +227,7 @@ public abstract class AbstractJsonMarshalTester<T> {
* @throws IOException on read error * @throws IOException on read error
*/ */
public T readObject(InputStream inputStream) throws IOException { public T readObject(InputStream inputStream) throws IOException {
verify();
return read(inputStream).getObject(); return read(inputStream).getObject();
} }
@ -210,6 +238,7 @@ public abstract class AbstractJsonMarshalTester<T> {
* @throws IOException on read error * @throws IOException on read error
*/ */
public ObjectContent<T> read(InputStream inputStream) throws IOException { public ObjectContent<T> read(InputStream inputStream) throws IOException {
verify();
Assert.notNull(inputStream, "InputStream must not be null"); Assert.notNull(inputStream, "InputStream must not be null");
return read(new InputStreamResource(inputStream)); return read(new InputStreamResource(inputStream));
} }
@ -221,6 +250,7 @@ public abstract class AbstractJsonMarshalTester<T> {
* @throws IOException on read error * @throws IOException on read error
*/ */
public T readObject(Resource resource) throws IOException { public T readObject(Resource resource) throws IOException {
verify();
return read(resource).getObject(); return read(resource).getObject();
} }
@ -231,6 +261,7 @@ public abstract class AbstractJsonMarshalTester<T> {
* @throws IOException on read error * @throws IOException on read error
*/ */
public ObjectContent<T> read(Resource resource) throws IOException { public ObjectContent<T> read(Resource resource) throws IOException {
verify();
Assert.notNull(resource, "Resource must not be null"); Assert.notNull(resource, "Resource must not be null");
InputStream inputStream = resource.getInputStream(); InputStream inputStream = resource.getInputStream();
T object = readObject(inputStream, this.type); T object = readObject(inputStream, this.type);
@ -245,6 +276,7 @@ public abstract class AbstractJsonMarshalTester<T> {
* @throws IOException on read error * @throws IOException on read error
*/ */
public T readObject(Reader reader) throws IOException { public T readObject(Reader reader) throws IOException {
verify();
return read(reader).getObject(); return read(reader).getObject();
} }
@ -255,6 +287,7 @@ public abstract class AbstractJsonMarshalTester<T> {
* @throws IOException on read error * @throws IOException on read error
*/ */
public ObjectContent<T> read(Reader reader) throws IOException { public ObjectContent<T> read(Reader reader) throws IOException {
verify();
Assert.notNull(reader, "Reader must not be null"); Assert.notNull(reader, "Reader must not be null");
T object = readObject(reader, this.type); T object = readObject(reader, this.type);
closeQuietly(reader); closeQuietly(reader);
@ -269,6 +302,12 @@ public abstract class AbstractJsonMarshalTester<T> {
} }
} }
private void verify() {
Assert.state(this.resourceLoadClass != null,
"Unitialized JsonMarshalTester (ResourceLoadClass is null)");
Assert.state(this.type != null, "Unitialized JsonMarshalTester (Type is null)");
}
/** /**
* Write the specified object to a JSON string. * Write the specified object to a JSON string.
* @param value the source value (never {@code null}) * @param value the source value (never {@code null})

View File

@ -19,6 +19,7 @@ package org.springframework.boot.test.json;
import java.io.File; import java.io.File;
import java.io.InputStream; import java.io.InputStream;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -45,7 +46,13 @@ import org.springframework.util.Assert;
*/ */
public class BasicJsonTester { public class BasicJsonTester {
private final JsonLoader loader; private JsonLoader loader;
/**
* Create a new uninialized {@link BasicJsonTester} instance.
*/
protected BasicJsonTester() {
}
/** /**
* Create a new {@link BasicJsonTester} instance. * Create a new {@link BasicJsonTester} instance.
@ -56,6 +63,18 @@ public class BasicJsonTester {
this.loader = new JsonLoader(resourceLoadClass); this.loader = new JsonLoader(resourceLoadClass);
} }
/**
* Initialize the marshal tester for use.
* @param resourceLoadClass the source class used when loading relative classpath
* resources
* @param type the type under test
*/
protected final void initialize(Class<?> resourceLoadClass, ResolvableType type) {
if (this.loader == null) {
this.loader = new JsonLoader(resourceLoadClass);
}
}
/** /**
* Create JSON content from the specified String source. The source can contain the * Create JSON content from the specified String source. The source can contain the
* JSON itself or, if it ends with {@code .json}, the name of a resource to be loaded * JSON itself or, if it ends with {@code .json}, the name of a resource to be loaded
@ -64,6 +83,7 @@ public class BasicJsonTester {
* @return the JSON content * @return the JSON content
*/ */
public JsonContent<Object> from(CharSequence source) { public JsonContent<Object> from(CharSequence source) {
verify();
return getJsonContent(this.loader.getJson(source)); return getJsonContent(this.loader.getJson(source));
} }
@ -74,6 +94,7 @@ public class BasicJsonTester {
* @return the JSON content * @return the JSON content
*/ */
public JsonContent<Object> from(String path, Class<?> resourceLoadClass) { public JsonContent<Object> from(String path, Class<?> resourceLoadClass) {
verify();
return getJsonContent(this.loader.getJson(path, resourceLoadClass)); return getJsonContent(this.loader.getJson(path, resourceLoadClass));
} }
@ -83,6 +104,7 @@ public class BasicJsonTester {
* @return the JSON content * @return the JSON content
*/ */
public JsonContent<Object> from(byte[] source) { public JsonContent<Object> from(byte[] source) {
verify();
return getJsonContent(this.loader.getJson(source)); return getJsonContent(this.loader.getJson(source));
} }
@ -92,6 +114,7 @@ public class BasicJsonTester {
* @return the JSON content * @return the JSON content
*/ */
public JsonContent<Object> from(File source) { public JsonContent<Object> from(File source) {
verify();
return getJsonContent(this.loader.getJson(source)); return getJsonContent(this.loader.getJson(source));
} }
@ -101,6 +124,7 @@ public class BasicJsonTester {
* @return the JSON content * @return the JSON content
*/ */
public JsonContent<Object> from(InputStream source) { public JsonContent<Object> from(InputStream source) {
verify();
return getJsonContent(this.loader.getJson(source)); return getJsonContent(this.loader.getJson(source));
} }
@ -110,9 +134,14 @@ public class BasicJsonTester {
* @return the JSON content * @return the JSON content
*/ */
public JsonContent<Object> from(Resource source) { public JsonContent<Object> from(Resource source) {
verify();
return getJsonContent(this.loader.getJson(source)); return getJsonContent(this.loader.getJson(source));
} }
private void verify() {
Assert.state(this.loader != null, "Unitialized BasicJsonTester");
}
private JsonContent<Object> getJsonContent(String json) { private JsonContent<Object> getJsonContent(String json) {
return new JsonContent<Object>(this.loader.getResourceLoadClass(), null, json); return new JsonContent<Object>(this.loader.getResourceLoadClass(), null, json);
} }

View File

@ -57,6 +57,15 @@ public class GsonTester<T> extends AbstractJsonMarshalTester<T> {
private final Gson gson; private final Gson gson;
/**
* Create a new uninitialized {@link GsonTester} instance.
* @param gson the Gson instance
*/
protected GsonTester(Gson gson) {
Assert.notNull(gson, "Gson must not be null");
this.gson = gson;
}
/** /**
* Create a new {@link GsonTester} instance. * Create a new {@link GsonTester} instance.
* @param resourceLoadClass the source class used to load resources * @param resourceLoadClass the source class used to load resources

View File

@ -59,6 +59,15 @@ public class JacksonTester<T> extends AbstractJsonMarshalTester<T> {
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
/**
* Create a new {@link JacksonTester} instance.
* @param objectMapper the Jackson object mapper
*/
protected JacksonTester(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
}
/** /**
* Create a new {@link JacksonTester} instance. * Create a new {@link JacksonTester} instance.
* @param resourceLoadClass the source class used to load resources * @param resourceLoadClass the source class used to load resources