diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index da855db9f7b..acc24aa6e09 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -5011,12 +5011,13 @@ annotation. 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 `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] ---- import org.junit.*; import org.junit.runner.*; + import org.springframework.beans.factory.annotation.*; import org.springframework.boot.test.autoconfigure.json.*; import org.springframework.boot.test.context.*; import org.springframework.boot.test.json.*; @@ -5028,6 +5029,7 @@ helper fields on the test class will be automatically initialized when using `@J @JsonTest public class MyJsonTests { + @Autowired private JacksonTester json; @Test diff --git a/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/service/VehicleDetailsJsonTests.java b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/service/VehicleDetailsJsonTests.java index 7fe89d27d92..b1b2b6af5f4 100644 --- a/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/service/VehicleDetailsJsonTests.java +++ b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/service/VehicleDetailsJsonTests.java @@ -19,6 +19,7 @@ package sample.test.service; import org.junit.Test; 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.json.JacksonTester; import org.springframework.test.context.junit4.SpringRunner; @@ -34,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat; @JsonTest public class VehicleDetailsJsonTests { + @Autowired private JacksonTester json; @Test diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/AutoConfigureJsonTesters.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/AutoConfigureJsonTesters.java index 646e1b30c13..2711787eaeb 100644 --- a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/AutoConfigureJsonTesters.java +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/AutoConfigureJsonTesters.java @@ -18,42 +18,34 @@ package org.springframework.boot.test.autoconfigure.json; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; 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.GsonTester; 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 * auto-configuration of JSON testers. - *

- * 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 - * @see JsonTesterInitializationTestExecutionListener */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented -@Inherited +@ImportAutoConfiguration +@PropertyMapping("spring.test.jsontesters") public @interface AutoConfigureJsonTesters { /** - * If {@link BasicJsonTester}, {@link JacksonTester} and {@link GsonTester} fields - * should be initialized using marshallers from the {@link ApplicationContext}. - * @return if JSON tester fields should be initialized + * If {@link BasicJsonTester}, {@link JacksonTester} and {@link GsonTester} beans + * should be registered. Defaults to {@code true} + * @return if tester support is enabled */ - boolean initFields() default true; + boolean enabled() default true; } diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/JsonTesterInitializationTestExecutionListener.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/JsonTesterInitializationTestExecutionListener.java deleted file mode 100644 index 32e4d5a8793..00000000000 --- a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/JsonTesterInitializationTestExecutionListener.java +++ /dev/null @@ -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> INITIALIZERS; - - static { - Map> initializers = new LinkedHashMap>(); - 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> 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 void initializeJsonMarshalTesterFields(final TestContext testContext, - final Class marshallerClass, Initializer initializer) { - initializer.initialize(testContext, new ObjectFactory() { - - @Override - public T getObject() throws BeansException { - return testContext.getApplicationContext().getBean(marshallerClass); - } - - }); - } - - /** - * Strategy used to initialize JSON testers without cause class not found exceptions. - * @param the marshaller type - */ - interface Initializer { - - void initialize(TestContext testContext, ObjectFactory marshaller); - - } - - /** - * {@link Initializer} for {@link JacksonTester}. - */ - static class JacksonInitializer implements Initializer { - - @Override - public void initialize(TestContext testContext, - ObjectFactory marshaller) { - JacksonTester.initFields(testContext.getTestInstance(), marshaller); - } - - } - - /** - * {@link Initializer} for {@link GsonTester}. - */ - static class GsonInitializer implements Initializer { - - @Override - public void initialize(TestContext testContext, ObjectFactory marshaller) { - GsonTester.initFields(testContext.getTestInstance(), marshaller); - } - - } - -} diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/JsonTestersAutoConfiguration.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/JsonTestersAutoConfiguration.java new file mode 100644 index 00000000000..5391470b635 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/JsonTestersAutoConfiguration.java @@ -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 BasicJsonTesterFactoryBean() { + return new JsonTesterFactoryBean(BasicJsonTester.class, + null); + } + + @Bean + @Scope("prototype") + @ConditionalOnClass(ObjectMapper.class) + @ConditionalOnBean(ObjectMapper.class) + public FactoryBean> jacksonTesterFactoryBean(ObjectMapper mapper) { + return new JsonTesterFactoryBean, ObjectMapper>( + JacksonTester.class, mapper); + } + + @Bean + @Scope("prototype") + @ConditionalOnClass(ObjectMapper.class) + @ConditionalOnBean(Gson.class) + public FactoryBean> gsonTesterFactoryBean(Gson gson) { + return new JsonTesterFactoryBean, Gson>(GsonTester.class, gson); + } + + /** + * {@link FactoryBean} used to create JSON Tester instances. + */ + private class JsonTesterFactoryBean implements FactoryBean { + + 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); + } + } + } + + } + +} diff --git a/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories index e57708b7880..421da09f5f4 100644 --- a/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories @@ -17,6 +17,10 @@ org.springframework.boot.test.autoconfigure.json.AutoConfigureJson=\ org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\ 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 org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc=\ org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration,\ @@ -69,6 +73,5 @@ org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCus # Test Execution Listeners org.springframework.test.context.TestExecutionListener=\ 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.web.client.MockRestServiceServerResetTestExecutionListener diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/JsonTestIntegrationTests.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/JsonTestIntegrationTests.java index 0b165abace7..dd408343c5d 100644 --- a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/JsonTestIntegrationTests.java +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/JsonTestIntegrationTests.java @@ -19,6 +19,7 @@ package org.springframework.boot.test.autoconfigure.json; import org.junit.Test; 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.GsonTester; import org.springframework.boot.test.json.JacksonTester; @@ -35,12 +36,16 @@ import static org.assertj.core.api.Assertions.assertThat; @JsonTest public class JsonTestIntegrationTests { + @Autowired private BasicJsonTester basicJson; + @Autowired private JacksonTester jacksonBasicJson; + @Autowired private JacksonTester jacksonCustomJson; + @Autowired private GsonTester gsonJson; @Test diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/JsonTestWithAutoConfigureJsonTestersTests.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/JsonTestWithAutoConfigureJsonTestersTests.java index 4ecd1a99fff..06a74146762 100644 --- a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/JsonTestWithAutoConfigureJsonTestersTests.java +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/JsonTestWithAutoConfigureJsonTestersTests.java @@ -19,6 +19,7 @@ package org.springframework.boot.test.autoconfigure.json; import org.junit.Test; 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.GsonTester; import org.springframework.boot.test.json.JacksonTester; @@ -33,13 +34,16 @@ import static org.assertj.core.api.Assertions.assertThat; */ @RunWith(SpringRunner.class) @JsonTest -@AutoConfigureJsonTesters(initFields = false) +@AutoConfigureJsonTesters(enabled = false) public class JsonTestWithAutoConfigureJsonTestersTests { + @Autowired(required = false) private BasicJsonTester basicJson; + @Autowired(required = false) private JacksonTester jacksonTester; + @Autowired(required = false) private GsonTester gsonTester; @Test diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/JsonTesterInitializationTestExecutionListenerTests.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/JsonTesterInitializationTestExecutionListenerTests.java deleted file mode 100644 index c568ffdcc7b..00000000000 --- a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/JsonTesterInitializationTestExecutionListenerTests.java +++ /dev/null @@ -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 tester; - } - - @AutoConfigureJsonTesters - static class WithGsonTester { - - private GsonTester tester; - } - - @AutoConfigureJsonTesters(initFields = false) - static class WithInitFieldsFalse { - - private BasicJsonTester basicTester; - - private JacksonTester jacksonTester; - - private GsonTester gsonTester; - - } - - @AutoConfigureJsonTesters(initFields = true) - static class WithInitFieldsTrue { - - private BasicJsonTester tester; - - } - - static class WithoutAnnotation { - - private BasicJsonTester basicTester; - - private JacksonTester jacksonTester; - - private GsonTester gsonTester; - - } - -} diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/json/AbstractJsonMarshalTester.java b/spring-boot-test/src/main/java/org/springframework/boot/test/json/AbstractJsonMarshalTester.java index b99eea4c06d..b68f42e0242 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/json/AbstractJsonMarshalTester.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/json/AbstractJsonMarshalTester.java @@ -70,9 +70,15 @@ import org.springframework.util.ReflectionUtils.FieldCallback; */ public abstract class AbstractJsonMarshalTester { - 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. @@ -83,8 +89,20 @@ public abstract class AbstractJsonMarshalTester { public AbstractJsonMarshalTester(Class resourceLoadClass, ResolvableType type) { Assert.notNull(resourceLoadClass, "ResourceLoadClass must not be null"); Assert.notNull(type, "Type must not be null"); - this.resourceLoadClass = resourceLoadClass; - this.type = type; + 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.type = type; + } } /** @@ -102,6 +120,7 @@ public abstract class AbstractJsonMarshalTester { * @throws IOException on write error */ public JsonContent write(T value) throws IOException { + verify(); Assert.notNull(value, "Value must not be null"); String json = writeObject(value, this.type); return new JsonContent(this.resourceLoadClass, this.type, json); @@ -114,6 +133,7 @@ public abstract class AbstractJsonMarshalTester { * @throws IOException on parse error */ public T parseObject(byte[] jsonBytes) throws IOException { + verify(); return parse(jsonBytes).getObject(); } @@ -124,6 +144,7 @@ public abstract class AbstractJsonMarshalTester { * @throws IOException on parse error */ public ObjectContent parse(byte[] jsonBytes) throws IOException { + verify(); Assert.notNull(jsonBytes, "JsonBytes must not be null"); return read(new ByteArrayResource(jsonBytes)); } @@ -135,6 +156,7 @@ public abstract class AbstractJsonMarshalTester { * @throws IOException on parse error */ public T parseObject(String jsonString) throws IOException { + verify(); return parse(jsonString).getObject(); } @@ -145,6 +167,7 @@ public abstract class AbstractJsonMarshalTester { * @throws IOException on parse error */ public ObjectContent parse(String jsonString) throws IOException { + verify(); Assert.notNull(jsonString, "JsonString must not be null"); return read(new StringReader(jsonString)); } @@ -157,6 +180,7 @@ public abstract class AbstractJsonMarshalTester { * @throws IOException on read error */ public T readObject(String resourcePath) throws IOException { + verify(); return read(resourcePath).getObject(); } @@ -168,6 +192,7 @@ public abstract class AbstractJsonMarshalTester { * @throws IOException on read error */ public ObjectContent read(String resourcePath) throws IOException { + verify(); Assert.notNull(resourcePath, "ResourcePath must not be null"); return read(new ClassPathResource(resourcePath, this.resourceLoadClass)); } @@ -179,6 +204,7 @@ public abstract class AbstractJsonMarshalTester { * @throws IOException on read error */ public T readObject(File file) throws IOException { + verify(); return read(file).getObject(); } @@ -189,6 +215,7 @@ public abstract class AbstractJsonMarshalTester { * @throws IOException on read error */ public ObjectContent read(File file) throws IOException { + verify(); Assert.notNull(file, "File must not be null"); return read(new FileSystemResource(file)); } @@ -200,6 +227,7 @@ public abstract class AbstractJsonMarshalTester { * @throws IOException on read error */ public T readObject(InputStream inputStream) throws IOException { + verify(); return read(inputStream).getObject(); } @@ -210,6 +238,7 @@ public abstract class AbstractJsonMarshalTester { * @throws IOException on read error */ public ObjectContent read(InputStream inputStream) throws IOException { + verify(); Assert.notNull(inputStream, "InputStream must not be null"); return read(new InputStreamResource(inputStream)); } @@ -221,6 +250,7 @@ public abstract class AbstractJsonMarshalTester { * @throws IOException on read error */ public T readObject(Resource resource) throws IOException { + verify(); return read(resource).getObject(); } @@ -231,6 +261,7 @@ public abstract class AbstractJsonMarshalTester { * @throws IOException on read error */ public ObjectContent read(Resource resource) throws IOException { + verify(); Assert.notNull(resource, "Resource must not be null"); InputStream inputStream = resource.getInputStream(); T object = readObject(inputStream, this.type); @@ -245,6 +276,7 @@ public abstract class AbstractJsonMarshalTester { * @throws IOException on read error */ public T readObject(Reader reader) throws IOException { + verify(); return read(reader).getObject(); } @@ -255,6 +287,7 @@ public abstract class AbstractJsonMarshalTester { * @throws IOException on read error */ public ObjectContent read(Reader reader) throws IOException { + verify(); Assert.notNull(reader, "Reader must not be null"); T object = readObject(reader, this.type); closeQuietly(reader); @@ -269,6 +302,12 @@ public abstract class AbstractJsonMarshalTester { } } + 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. * @param value the source value (never {@code null}) diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/json/BasicJsonTester.java b/spring-boot-test/src/main/java/org/springframework/boot/test/json/BasicJsonTester.java index 68d51cc04a8..4cf6d84f246 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/json/BasicJsonTester.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/json/BasicJsonTester.java @@ -19,6 +19,7 @@ package org.springframework.boot.test.json; import java.io.File; import java.io.InputStream; +import org.springframework.core.ResolvableType; import org.springframework.core.io.Resource; import org.springframework.util.Assert; @@ -45,7 +46,13 @@ import org.springframework.util.Assert; */ 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. @@ -56,6 +63,18 @@ public class BasicJsonTester { 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 * 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 */ public JsonContent from(CharSequence source) { + verify(); return getJsonContent(this.loader.getJson(source)); } @@ -74,6 +94,7 @@ public class BasicJsonTester { * @return the JSON content */ public JsonContent from(String path, Class resourceLoadClass) { + verify(); return getJsonContent(this.loader.getJson(path, resourceLoadClass)); } @@ -83,6 +104,7 @@ public class BasicJsonTester { * @return the JSON content */ public JsonContent from(byte[] source) { + verify(); return getJsonContent(this.loader.getJson(source)); } @@ -92,6 +114,7 @@ public class BasicJsonTester { * @return the JSON content */ public JsonContent from(File source) { + verify(); return getJsonContent(this.loader.getJson(source)); } @@ -101,6 +124,7 @@ public class BasicJsonTester { * @return the JSON content */ public JsonContent from(InputStream source) { + verify(); return getJsonContent(this.loader.getJson(source)); } @@ -110,9 +134,14 @@ public class BasicJsonTester { * @return the JSON content */ public JsonContent from(Resource source) { + verify(); return getJsonContent(this.loader.getJson(source)); } + private void verify() { + Assert.state(this.loader != null, "Unitialized BasicJsonTester"); + } + private JsonContent getJsonContent(String json) { return new JsonContent(this.loader.getResourceLoadClass(), null, json); } diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/json/GsonTester.java b/spring-boot-test/src/main/java/org/springframework/boot/test/json/GsonTester.java index 7978a0de94e..52adec92ed5 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/json/GsonTester.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/json/GsonTester.java @@ -57,6 +57,15 @@ public class GsonTester extends AbstractJsonMarshalTester { 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. * @param resourceLoadClass the source class used to load resources diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/json/JacksonTester.java b/spring-boot-test/src/main/java/org/springframework/boot/test/json/JacksonTester.java index 6157f2258c1..466d8e9d112 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/json/JacksonTester.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/json/JacksonTester.java @@ -59,6 +59,15 @@ public class JacksonTester extends AbstractJsonMarshalTester { 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. * @param resourceLoadClass the source class used to load resources