/* * Copyright 2002-2017 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.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.ContainerExtensionContext; 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.TestExtensionContext; 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.test.context.TestContextManager; import org.springframework.util.Assert; /** * {@code SpringExtension} integrates the <em>Spring TestContext Framework</em> * into JUnit 5's <em>Jupiter</em> programming model. * * <p>To use this class, simply annotate a JUnit Jupiter based test class with * {@code @ExtendWith(SpringExtension.class)}. * * @author Sam Brannen * @since 5.0 * @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(ContainerExtensionContext context) throws Exception { getTestContextManager(context).beforeTestClass(); } /** * Delegates to {@link TestContextManager#afterTestClass}. */ @Override public void afterAll(ContainerExtensionContext context) throws Exception { try { getTestContextManager(context).afterTestClass(); } finally { context.getStore(NAMESPACE).remove(context.getTestClass().get()); } } /** * 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(TestExtensionContext context) throws Exception { Object testInstance = context.getTestInstance(); Method testMethod = context.getTestMethod().get(); getTestContextManager(context).beforeTestMethod(testInstance, testMethod); } /** * Delegates to {@link TestContextManager#beforeTestExecution}. */ @Override public void beforeTestExecution(TestExtensionContext context) throws Exception { Object testInstance = context.getTestInstance(); Method testMethod = context.getTestMethod().get(); getTestContextManager(context).beforeTestExecution(testInstance, testMethod); } /** * Delegates to {@link TestContextManager#afterTestExecution}. */ @Override public void afterTestExecution(TestExtensionContext context) throws Exception { Object testInstance = context.getTestInstance(); Method testMethod = context.getTestMethod().get(); Throwable testException = context.getTestException().orElse(null); getTestContextManager(context).afterTestExecution(testInstance, testMethod, testException); } /** * Delegates to {@link TestContextManager#afterTestMethod}. */ @Override public void afterEach(TestExtensionContext context) throws Exception { Object testInstance = context.getTestInstance(); Method testMethod = context.getTestMethod().get(); Throwable testException = context.getTestException().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}. * <p>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}. * <p><strong>WARNING</strong>: 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 #resolve * @see ParameterAutowireUtils#isAutowirable */ @Override public boolean supports(ParameterContext parameterContext, ExtensionContext extensionContext) { Parameter parameter = parameterContext.getParameter(); Executable executable = parameter.getDeclaringExecutable(); return (executable instanceof Constructor && AnnotatedElementUtils.hasAnnotation(executable, Autowired.class)) || ParameterAutowireUtils.isAutowirable(parameter); } /** * Resolve a value for the {@link Parameter} in the supplied {@link ParameterContext} by * retrieving the corresponding dependency from the test's {@link ApplicationContext}. * <p>Delegates to {@link ParameterAutowireUtils#resolveDependency}. * @see #supports * @see ParameterAutowireUtils#resolveDependency */ @Override public Object resolve(ParameterContext parameterContext, ExtensionContext extensionContext) { Parameter parameter = parameterContext.getParameter(); Class<?> testClass = extensionContext.getTestClass().get(); ApplicationContext applicationContext = getApplicationContext(extensionContext); return ParameterAutowireUtils.resolveDependency(parameter, 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.getTestClass().get(); Store store = context.getStore(NAMESPACE); return store.getOrComputeIfAbsent(testClass, TestContextManager::new, TestContextManager.class); } }