Change @TestConstructor.autowire attribute into an enum

Prior to this commit, @TestConstructor supported a boolean `autowire`
attribute which naturally limited the configuration to two states: on
or off. Since we may need to support additional autowiring modes in the
future, the use of a boolean is limiting.

This commit address this issue by introducing a new AutowireMode enum
in @TestConstructor with ALL and ANNOTATED constants. In addition, the
attribute has been renamed to `autowireMode`, and the system property
has been renamed to `spring.test.constructor.autowire.mode` for greater
clarity of purpose.

Closes gh-23224
This commit is contained in:
Sam Brannen
2019-07-11 18:00:52 +02:00
parent c8f8dfa39e
commit fc38bb4fc6
5 changed files with 145 additions and 51 deletions

View File

@@ -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}.
*
* <p>If {@code @TestConstructor} is not <em>present</em> or <em>meta-present</em>
* on a test class, the default <em>test constructor autowire</em> 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 <em>test constructor autowire mode</em> 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;
* <p>This annotation may be used as a <em>meta-annotation</em> to create custom
* <em>composed annotations</em>.
*
* <p>As of Spring Framework 5.2, this annotation is only supported in conjunction with
* the {@link org.springframework.test.context.junit.jupiter.SpringExtension
* <p>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 &mdash; 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 <em>test constructor autowire</em>
* mode: {@value #TEST_CONSTRUCTOR_AUTOWIRE_PROPERTY_NAME}.
* JVM system property used to change the default <em>test constructor
* autowire mode</em>: {@value #TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME}.
* <p>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.
* <pre style="code">-Dspring.test.constructor.autowire.mode=all</pre>
* <p>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.
* <p>May alternatively be configured via the
* {@link org.springframework.core.SpringProperties SpringProperties} mechanism.
* <p>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 <em>test constructor autowire</em> mode for the
* current test class.
* Flag for setting the <em>test constructor {@linkplain AutowireMode autowire
* mode}</em> for the current test class.
* <p>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;
}
}

View File

@@ -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 {
* <li>The constructor is annotated with {@link Autowired @Autowired}.</li>
* <li>{@link TestConstructor @TestConstructor} is <em>present</em> or
* <em>meta-present</em> on the test class with
* {@link TestConstructor#autowire autowire} set to {@code true}.</li>
* <li>The default <em>test constructor autowire</em> mode is set to {@code true}
* (see {@link TestConstructor#TEST_CONSTRUCTOR_AUTOWIRE_PROPERTY_NAME}).</li>
* {@link TestConstructor#autowireMode() autowireMode} set to
* {@link AutowireMode#ALL ALL}.</li>
* <li>The default <em>test constructor autowire mode</em> has been changed
* to {@code ALL} (see
* {@link TestConstructor#TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME}).</li>
* </ol>
*
* @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);
}
}