org.springframework.test.context.junit.jupiter.SpringExtension Maven / Gradle / Ivy
Show all versions of spring-test Show documentation
/*
* Copyright 2002-2018 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
*
* https://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.test.context.junit.jupiter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.lang.Nullable;
import org.springframework.test.context.TestContextManager;
import org.springframework.util.Assert;
/**
* {@code SpringExtension} integrates the Spring TestContext Framework
* into JUnit 5's Jupiter programming model.
*
* To use this extension, simply annotate a JUnit Jupiter based test class with
* {@code @ExtendWith(SpringExtension.class)}, {@code @SpringJUnitConfig}, or
* {@code @SpringJUnitWebConfig}.
*
* @author Sam Brannen
* @since 5.0
* @see org.springframework.test.context.junit.jupiter.EnabledIf
* @see org.springframework.test.context.junit.jupiter.DisabledIf
* @see org.springframework.test.context.junit.jupiter.SpringJUnitConfig
* @see org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig
* @see org.springframework.test.context.TestContextManager
*/
public class SpringExtension implements BeforeAllCallback, AfterAllCallback, TestInstancePostProcessor,
BeforeEachCallback, AfterEachCallback, BeforeTestExecutionCallback, AfterTestExecutionCallback,
ParameterResolver {
/**
* {@link Namespace} in which {@code TestContextManagers} are stored,
* keyed by test class.
*/
private static final Namespace NAMESPACE = Namespace.create(SpringExtension.class);
/**
* Delegates to {@link TestContextManager#beforeTestClass}.
*/
@Override
public void beforeAll(ExtensionContext context) throws Exception {
getTestContextManager(context).beforeTestClass();
}
/**
* Delegates to {@link TestContextManager#afterTestClass}.
*/
@Override
public void afterAll(ExtensionContext context) throws Exception {
try {
getTestContextManager(context).afterTestClass();
}
finally {
getStore(context).remove(context.getRequiredTestClass());
}
}
/**
* Delegates to {@link TestContextManager#prepareTestInstance}.
*/
@Override
public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {
getTestContextManager(context).prepareTestInstance(testInstance);
}
/**
* Delegates to {@link TestContextManager#beforeTestMethod}.
*/
@Override
public void beforeEach(ExtensionContext context) throws Exception {
Object testInstance = context.getRequiredTestInstance();
Method testMethod = context.getRequiredTestMethod();
getTestContextManager(context).beforeTestMethod(testInstance, testMethod);
}
/**
* Delegates to {@link TestContextManager#beforeTestExecution}.
*/
@Override
public void beforeTestExecution(ExtensionContext context) throws Exception {
Object testInstance = context.getRequiredTestInstance();
Method testMethod = context.getRequiredTestMethod();
getTestContextManager(context).beforeTestExecution(testInstance, testMethod);
}
/**
* Delegates to {@link TestContextManager#afterTestExecution}.
*/
@Override
public void afterTestExecution(ExtensionContext context) throws Exception {
Object testInstance = context.getRequiredTestInstance();
Method testMethod = context.getRequiredTestMethod();
Throwable testException = context.getExecutionException().orElse(null);
getTestContextManager(context).afterTestExecution(testInstance, testMethod, testException);
}
/**
* Delegates to {@link TestContextManager#afterTestMethod}.
*/
@Override
public void afterEach(ExtensionContext context) throws Exception {
Object testInstance = context.getRequiredTestInstance();
Method testMethod = context.getRequiredTestMethod();
Throwable testException = context.getExecutionException().orElse(null);
getTestContextManager(context).afterTestMethod(testInstance, testMethod, testException);
}
/**
* Determine if the value for the {@link Parameter} in the supplied {@link ParameterContext}
* should be autowired from the test's {@link ApplicationContext}.
*
Returns {@code true} if the parameter is declared in a {@link Constructor}
* that is annotated with {@link Autowired @Autowired} and otherwise delegates to
* {@link ParameterAutowireUtils#isAutowirable}.
*
WARNING: If the parameter is declared in a {@code Constructor}
* that is annotated with {@code @Autowired}, Spring will assume the responsibility
* for resolving all parameters in the constructor. Consequently, no other registered
* {@link ParameterResolver} will be able to resolve parameters.
* @see #resolveParameter
* @see ParameterAutowireUtils#isAutowirable
*/
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
Parameter parameter = parameterContext.getParameter();
int index = parameterContext.getIndex();
Executable executable = parameter.getDeclaringExecutable();
return (executable instanceof Constructor &&
AnnotatedElementUtils.hasAnnotation(executable, Autowired.class)) ||
ParameterAutowireUtils.isAutowirable(parameter, index);
}
/**
* Resolve a value for the {@link Parameter} in the supplied {@link ParameterContext} by
* retrieving the corresponding dependency from the test's {@link ApplicationContext}.
*
Delegates to {@link ParameterAutowireUtils#resolveDependency}.
* @see #supportsParameter
* @see ParameterAutowireUtils#resolveDependency
*/
@Override
@Nullable
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
Parameter parameter = parameterContext.getParameter();
int index = parameterContext.getIndex();
Class testClass = extensionContext.getRequiredTestClass();
ApplicationContext applicationContext = getApplicationContext(extensionContext);
return ParameterAutowireUtils.resolveDependency(parameter, index, testClass, applicationContext);
}
/**
* Get the {@link ApplicationContext} associated with the supplied {@code ExtensionContext}.
* @param context the current {@code ExtensionContext} (never {@code null})
* @return the application context
* @throws IllegalStateException if an error occurs while retrieving the application context
* @see org.springframework.test.context.TestContext#getApplicationContext()
*/
public static ApplicationContext getApplicationContext(ExtensionContext context) {
return getTestContextManager(context).getTestContext().getApplicationContext();
}
/**
* Get the {@link TestContextManager} associated with the supplied {@code ExtensionContext}.
* @return the {@code TestContextManager} (never {@code null})
*/
private static TestContextManager getTestContextManager(ExtensionContext context) {
Assert.notNull(context, "ExtensionContext must not be null");
Class testClass = context.getRequiredTestClass();
Store store = getStore(context);
return store.getOrComputeIfAbsent(testClass, TestContextManager::new, TestContextManager.class);
}
private static Store getStore(ExtensionContext context) {
return context.getRoot().getStore(NAMESPACE);
}
}