Why does @SpringBootTest need @Autowired in constructor injection

Viewed 885

A more general question. If one uses constructor injection in a regular Spring managed class the classes get autowired without requiring the @Autowired annotation, i. e.:

@Service
class MailService(
  private val projectService: ProjectService,
  private val mailer: Mailer
) { ... }

Following the same constructor injection principle in a @SpringBootTest class, you need to have the @Autowired annotation set to the constructor parameter otherwise it will fail to inject the class, i. e.:

@SpringBootTest
internal class MailerTest(
  @Autowired private val mailer: Mailer
) { ... }

Why does this difference occur?

1 Answers

In case of SpringBoot application, it is spring that is responsible for wiring up beans.

In case of JUnit 5, Beans managed by Spring must be injected to the instance of test class managed by JUnit. Luckily, JUnit 5 provides a way to do that via a ParameterResolver.

@SpringBootTest registers SpringExtension, which, among other functionalities works as a ParameterResolver:

@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
    Parameter parameter = parameterContext.getParameter();
    Executable executable = parameter.getDeclaringExecutable();
    Class<?> testClass = extensionContext.getRequiredTestClass();
    PropertyProvider junitPropertyProvider = propertyName ->
    extensionContext.getConfigurationParameter(propertyName).orElse(null);
    return (TestConstructorUtils.isAutowirableConstructor(executable, testClass, junitPropertyProvider) ||
            ApplicationContext.class.isAssignableFrom(parameter.getType()) ||
            supportsApplicationEvents(parameterContext) ||
            ParameterResolutionDelegate.isAutowirable(parameter, parameterContext.getIndex()));
}

ParameterResolutionDelegate.isAutowirable relies on annotations to find out if parameters can be injected from Spring's ApplicationContext

public static boolean isAutowirable(Parameter parameter, int parameterIndex) {
    Assert.notNull(parameter, "Parameter must not be null");
    AnnotatedElement annotatedParameter = getEffectiveAnnotatedParameter(parameter, parameterIndex);
    return (AnnotatedElementUtils.hasAnnotation(annotatedParameter, Autowired.class) ||
            AnnotatedElementUtils.hasAnnotation(annotatedParameter, Qualifier.class) ||
            AnnotatedElementUtils.hasAnnotation(annotatedParameter, Value.class));
}

In fact, if you omit the @Autowired annotation, JUnit will complain about missing ParameterResolver:

org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter [test.Mailer mailer] in constructor [public test.MailServiceTest(test.Mailer)].
Related