diff --git a/spring-test/src/main/java/org/springframework/test/context/TestConstructor.java b/spring-test/src/main/java/org/springframework/test/context/TestConstructor.java index 461eaa4e299..60a5a34a669 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestConstructor.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestConstructor.java @@ -25,14 +25,14 @@ import java.lang.annotation.Target; /** * {@code @TestConstructor} is a type-level annotation that is used to configure - * whether a test class constructor should be automatically autowired from - * components in the test's {@link org.springframework.context.ApplicationContext + * how the parameters of a test class constructor are autowired from components + * in the test's {@link org.springframework.context.ApplicationContext * ApplicationContext}. * *

If {@code @TestConstructor} is not present or meta-present - * on a test class, the default test constructor autowire mode will be used. - * See {@link #TEST_CONSTRUCTOR_AUTOWIRE_PROPERTY_NAME} for details on how to change - * the default mode. Note, however, that a local declaration of + * on a test class, the default test constructor autowire mode will be + * used. See {@link #TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME} for details on + * how to change the default mode. Note, however, that a local declaration of * {@link org.springframework.beans.factory.annotation.Autowired @Autowired} on * a constructor takes precedence over both {@code @TestConstructor} and the default * mode. @@ -40,8 +40,8 @@ import java.lang.annotation.Target; *

This annotation may be used as a meta-annotation to create custom * composed annotations. * - *

As of Spring Framework 5.2, this annotation is only supported in conjunction with - * the {@link org.springframework.test.context.junit.jupiter.SpringExtension + *

As of Spring Framework 5.2, this annotation is only supported in conjunction + * with the {@link org.springframework.test.context.junit.jupiter.SpringExtension * SpringExtension} for use with JUnit Jupiter. Note that the {@code SpringExtension} is * often automatically registered for you — for example, when using annotations such as * {@link org.springframework.test.context.junit.jupiter.SpringJUnitConfig @SpringJUnitConfig} and @@ -66,29 +66,64 @@ import java.lang.annotation.Target; public @interface TestConstructor { /** - * System property used to configure the default test constructor autowire - * mode: {@value #TEST_CONSTRUCTOR_AUTOWIRE_PROPERTY_NAME}. + * JVM system property used to change the default test constructor + * autowire mode: {@value #TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME}. + *

Acceptable values include enum constants defined in {@link AutowireMode}, + * ignoring case. For example, the default may be changed to {@link AutowireMode#ALL} + * by supplying the following JVM system property via the command line. + *

-Dspring.test.constructor.autowire.mode=all
+ *

If the property is not set to {@code ALL}, parameters for test class + * constructors will be autowired according to {@link AutowireMode#ANNOTATED} + * semantics by default. *

May alternatively be configured via the - * {@link org.springframework.core.SpringProperties SpringProperties} mechanism. - *

If the property is not set, test class constructors will not be automatically - * autowired. - * @see #autowire + * {@link org.springframework.core.SpringProperties SpringProperties} + * mechanism. + * @see #autowireMode */ - String TEST_CONSTRUCTOR_AUTOWIRE_PROPERTY_NAME = "spring.test.constructor.autowire"; + String TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME = "spring.test.constructor.autowire.mode"; /** - * Flag for setting the test constructor autowire mode for the - * current test class. + * Flag for setting the test constructor {@linkplain AutowireMode autowire + * mode} for the current test class. *

Setting this flag overrides the global default. See - * {@link #TEST_CONSTRUCTOR_AUTOWIRE_PROPERTY_NAME} for details on how to - * change the global default. - * @return {@code true} if all test constructor arguments should be autowired - * from the test's {@link org.springframework.context.ApplicationContext - * ApplicationContext} - * @see #TEST_CONSTRUCTOR_AUTOWIRE_PROPERTY_NAME + * {@link #TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME} for details on how + * to change the global default. + * @return an {@link AutowireMode} to take precedence over the global default + * @see #TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME * @see org.springframework.beans.factory.annotation.Autowired @Autowired + * @see AutowireMode#ALL + * @see AutowireMode#ANNOTATED */ - boolean autowire(); + AutowireMode autowireMode(); + + + /** + * Defines autowiring modes for parameters in a test constructor. + * @see #ALL + * @see #ANNOTATED + */ + enum AutowireMode { + + /** + * All test constructor parameters will be autowired as if the constructor + * itself were annotated with + * {@link org.springframework.beans.factory.annotation.Autowired @Autowired}. + * @see #ANNOTATED + */ + ALL, + + /** + * Each individual test constructor parameter will only be autowired if it + * is annotated with + * {@link org.springframework.beans.factory.annotation.Autowired @Autowired}, + * {@link org.springframework.beans.factory.annotation.Qualifier @Qualifier}, + * or {@link org.springframework.beans.factory.annotation.Value @Value}, + * or if the constructor itself is annotated with {@code @Autowired}. + * @see #ALL + */ + ANNOTATED; + + } } diff --git a/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java index d946033676e..a11e8afb7a2 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java @@ -19,10 +19,14 @@ package org.springframework.test.context.support; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.SpringProperties; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.test.context.TestConstructor; +import org.springframework.test.context.TestConstructor.AutowireMode; /** * Utility methods for working with {@link TestConstructor @TestConstructor}. @@ -35,10 +39,12 @@ import org.springframework.test.context.TestConstructor; */ public abstract class TestConstructorUtils { + private static final Log logger = LogFactory.getLog(TestConstructorUtils.class); + + private TestConstructorUtils() { } - /** * Determine if the supplied executable for the given test class is an * autowirable constructor. @@ -67,9 +73,11 @@ public abstract class TestConstructorUtils { *

  • The constructor is annotated with {@link Autowired @Autowired}.
  • *
  • {@link TestConstructor @TestConstructor} is present or * meta-present on the test class with - * {@link TestConstructor#autowire autowire} set to {@code true}.
  • - *
  • The default test constructor autowire mode is set to {@code true} - * (see {@link TestConstructor#TEST_CONSTRUCTOR_AUTOWIRE_PROPERTY_NAME}).
  • + * {@link TestConstructor#autowireMode() autowireMode} set to + * {@link AutowireMode#ALL ALL}. + *
  • The default test constructor autowire mode has been changed + * to {@code ALL} (see + * {@link TestConstructor#TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME}).
  • * * * @param constructor a constructor for the test class @@ -82,13 +90,32 @@ public abstract class TestConstructorUtils { if (AnnotatedElementUtils.hasAnnotation(constructor, Autowired.class)) { return true; } + + // Default to Autowire.ANNOTATED semantics. + AutowireMode autowireMode = AutowireMode.ANNOTATED; + // Is the test class annotated with @TestConstructor? TestConstructor testConstructor = AnnotatedElementUtils.findMergedAnnotation(testClass, TestConstructor.class); if (testConstructor != null) { - return testConstructor.autowire(); + autowireMode = testConstructor.autowireMode(); } - // Else use global default. - return SpringProperties.getFlag(TestConstructor.TEST_CONSTRUCTOR_AUTOWIRE_PROPERTY_NAME); + else { + // Custom global default? + String value = SpringProperties.getProperty(TestConstructor.TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME); + if (value != null) { + try { + autowireMode = AutowireMode.valueOf(value.trim().toUpperCase()); + } + catch (Exception ex) { + if (logger.isDebugEnabled()) { + logger.debug(String.format("Failed to parse autowire mode '%s' for property '%s': %s", value, + TestConstructor.TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME, ex.getMessage())); + } + } + } + } + + return (autowireMode == AutowireMode.ALL); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/TestConstructorAnnotationIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/TestConstructorAnnotationIntegrationTests.java index 6c5f12bf74d..7faa8450274 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/TestConstructorAnnotationIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/TestConstructorAnnotationIntegrationTests.java @@ -28,6 +28,7 @@ import org.springframework.test.context.junit.jupiter.comics.Dog; import org.springframework.test.context.junit.jupiter.comics.Person; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.context.TestConstructor.AutowireMode.ALL; /** * Integration tests which demonstrate support for automatically @@ -45,7 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat; */ @SpringJUnitConfig(TestConfig.class) @TestPropertySource(properties = "enigma = 42") -@TestConstructor(autowire = true) +@TestConstructor(autowireMode = ALL) class TestConstructorAnnotationIntegrationTests { final ApplicationContext applicationContext; @@ -53,6 +54,7 @@ class TestConstructorAnnotationIntegrationTests { final Dog dog; final Integer enigma; + TestConstructorAnnotationIntegrationTests(ApplicationContext applicationContext, Person dilbert, Dog dog, @Value("${enigma}") Integer enigma) { diff --git a/spring-test/src/test/java/org/springframework/test/context/support/TestConstructorUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/support/TestConstructorUtilsTests.java index 8904ad18611..e1d8ee60049 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/TestConstructorUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/TestConstructorUtilsTests.java @@ -22,9 +22,13 @@ import org.junit.After; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.SpringProperties; import org.springframework.test.context.TestConstructor; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.context.TestConstructor.TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME; +import static org.springframework.test.context.TestConstructor.AutowireMode.ALL; +import static org.springframework.test.context.TestConstructor.AutowireMode.ANNOTATED; /** * Unit tests for {@link TestConstructorUtils}. @@ -36,7 +40,7 @@ public class TestConstructorUtilsTests { @After public void clearGlobalFlag() { - System.clearProperty(TestConstructor.TEST_CONSTRUCTOR_AUTOWIRE_PROPERTY_NAME); + setGlobalFlag(null); } @Test @@ -66,6 +70,26 @@ public class TestConstructorUtilsTests { assertNotAutowirable(TestConstructorAnnotationOverridesGlobalFlagTestCase.class); } + @Test + public void globalFlagVariations() throws Exception { + Class testClass = AutomaticallyAutowiredTestCase.class; + + setGlobalFlag(ALL.name()); + assertAutowirable(testClass); + + setGlobalFlag(ALL.name().toLowerCase()); + assertAutowirable(testClass); + + setGlobalFlag("\t" + ALL.name().toLowerCase() + " "); + assertAutowirable(testClass); + + setGlobalFlag("bogus"); + assertNotAutowirable(testClass); + + setGlobalFlag(" "); + assertNotAutowirable(testClass); + } + private void assertAutowirable(Class testClass) throws NoSuchMethodException { Constructor constructor = testClass.getDeclaredConstructor(); assertThat(TestConstructorUtils.isAutowirableConstructor(constructor, testClass)).isTrue(); @@ -77,14 +101,20 @@ public class TestConstructorUtilsTests { } private void setGlobalFlag() { - System.setProperty(TestConstructor.TEST_CONSTRUCTOR_AUTOWIRE_PROPERTY_NAME, "true"); + setGlobalFlag(ALL.name()); + } + + private void setGlobalFlag(String flag) { + SpringProperties.setProperty(TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME, flag); } static class NotAutowirableTestCase { } - @TestConstructor(autowire = false) + // The following declaration simply verifies that @Autowired on the constructor takes + // precedence. + @TestConstructor(autowireMode = ANNOTATED) static class AutowiredAnnotationTestCase { @Autowired @@ -92,14 +122,14 @@ public class TestConstructorUtilsTests { } } - @TestConstructor(autowire = true) + @TestConstructor(autowireMode = ALL) static class TestConstructorAnnotationTestCase { } static class AutomaticallyAutowiredTestCase { } - @TestConstructor(autowire = false) + @TestConstructor(autowireMode = ANNOTATED) static class TestConstructorAnnotationOverridesGlobalFlagTestCase { } diff --git a/src/docs/asciidoc/testing.adoc b/src/docs/asciidoc/testing.adoc index 70826cb53b2..d7970a419e0 100644 --- a/src/docs/asciidoc/testing.adoc +++ b/src/docs/asciidoc/testing.adoc @@ -1287,24 +1287,24 @@ for further details. [[integration-testing-annotations-testconstructor]] ===== `@TestConstructor` -`@TestConstructor` is a type-level annotation that is used to configure whether a test -class constructor should be automatically autowired from components in the test's +`@TestConstructor` is a type-level annotation that is used to configure how the parameters +of a test class constructor are autowired from components in the test's `ApplicationContext`. If `@TestConstructor` is not present or meta-present on a test class, the default _test -constructor autowire_ mode will be used. See the tip below for details on how to change +constructor autowire mode_ will be used. See the tip below for details on how to change the default mode. Note, however, that a local declaration of `@Autowired` on a constructor takes precedence over both `@TestConstructor` and the default mode. -.Configuring the default test constructor autowire mode +.Changing the default test constructor autowire mode [TIP] ===== -The default _test constructor autowire_ mode can be configured by setting the -`spring.test.constructor.autowire` JVM system property to `true`. Alternatively, the -default mode may be configured via the `SpringProperties` mechanism. +The default _test constructor autowire mode_ can be changed by setting the +`spring.test.constructor.autowire.mode` JVM system property to `all`. Alternatively, the +default mode may be changed via the `SpringProperties` mechanism. -If the `spring.test.constructor.autowire` property is not set, test class constructors -will not be automatically autowired. +If the `spring.test.constructor.autowire.mode` property is not set, test class +constructors will not be automatically autowired. ===== NOTE: As of Spring Framework 5.2, `@TestConstructor` is only supported in conjunction @@ -4486,12 +4486,12 @@ the constructor is considered to be _autowirable_. A constructor is considered t autowirable if one of the following conditions is met (in order of precedence). * The constructor is annotated with `@Autowired`. -* `@TestConstructor` is present or meta-present on the test class with the `autowire` - attribute set to `true`. -* The default _test constructor autowire_ mode is set to `true`. +* `@TestConstructor` is present or meta-present on the test class with the `autowireMode` + attribute set to `ALL`. +* The default _test constructor autowire mode_ has been changed to `ALL`. See <> for details on the use of -`@TestConstructor` and how to set the global _test constructor autowire_ mode. +`@TestConstructor` and how to change the global _test constructor autowire mode_. WARNING: If the constructor for a test class is considered to be _autowirable_, Spring assumes the responsibility for resolving arguments for all parameters in the constructor. @@ -4540,9 +4540,9 @@ In the following example, Spring injects the `OrderService` bean from the Note that this feature lets test dependencies be `final` and therefore immutable. -If the `spring.test.constructor.autowire` property is to `true` (see +If the `spring.test.constructor.autowire.mode` property is to `all` (see <>), we can omit the declaration of -`@Autowired` on the constructor in the previous example resulting in the following. +`@Autowired` on the constructor in the previous example, resulting in the following. [source,java,indent=0] [subs="verbatim,quotes"]