001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.test.context.junit.jupiter;
018
019import java.lang.reflect.Constructor;
020import java.lang.reflect.Executable;
021import java.lang.reflect.Method;
022import java.lang.reflect.Parameter;
023
024import org.junit.jupiter.api.extension.AfterAllCallback;
025import org.junit.jupiter.api.extension.AfterEachCallback;
026import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
027import org.junit.jupiter.api.extension.BeforeAllCallback;
028import org.junit.jupiter.api.extension.BeforeEachCallback;
029import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
030import org.junit.jupiter.api.extension.ExtensionContext;
031import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
032import org.junit.jupiter.api.extension.ExtensionContext.Store;
033import org.junit.jupiter.api.extension.ParameterContext;
034import org.junit.jupiter.api.extension.ParameterResolver;
035import org.junit.jupiter.api.extension.TestInstancePostProcessor;
036
037import org.springframework.beans.factory.annotation.ParameterResolutionDelegate;
038import org.springframework.context.ApplicationContext;
039import org.springframework.lang.Nullable;
040import org.springframework.test.context.TestConstructor;
041import org.springframework.test.context.TestContextManager;
042import org.springframework.test.context.support.TestConstructorUtils;
043import org.springframework.util.Assert;
044
045/**
046 * {@code SpringExtension} integrates the <em>Spring TestContext Framework</em>
047 * into JUnit 5's <em>Jupiter</em> programming model.
048 *
049 * <p>To use this extension, simply annotate a JUnit Jupiter based test class with
050 * {@code @ExtendWith(SpringExtension.class)}, {@code @SpringJUnitConfig}, or
051 * {@code @SpringJUnitWebConfig}.
052 *
053 * @author Sam Brannen
054 * @since 5.0
055 * @see org.springframework.test.context.junit.jupiter.EnabledIf
056 * @see org.springframework.test.context.junit.jupiter.DisabledIf
057 * @see org.springframework.test.context.junit.jupiter.SpringJUnitConfig
058 * @see org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig
059 * @see org.springframework.test.context.TestContextManager
060 */
061public class SpringExtension implements BeforeAllCallback, AfterAllCallback, TestInstancePostProcessor,
062                BeforeEachCallback, AfterEachCallback, BeforeTestExecutionCallback, AfterTestExecutionCallback,
063                ParameterResolver {
064
065        /**
066         * {@link Namespace} in which {@code TestContextManagers} are stored,
067         * keyed by test class.
068         */
069        private static final Namespace NAMESPACE = Namespace.create(SpringExtension.class);
070
071
072        /**
073         * Delegates to {@link TestContextManager#beforeTestClass}.
074         */
075        @Override
076        public void beforeAll(ExtensionContext context) throws Exception {
077                getTestContextManager(context).beforeTestClass();
078        }
079
080        /**
081         * Delegates to {@link TestContextManager#afterTestClass}.
082         */
083        @Override
084        public void afterAll(ExtensionContext context) throws Exception {
085                try {
086                        getTestContextManager(context).afterTestClass();
087                }
088                finally {
089                        getStore(context).remove(context.getRequiredTestClass());
090                }
091        }
092
093        /**
094         * Delegates to {@link TestContextManager#prepareTestInstance}.
095         */
096        @Override
097        public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {
098                getTestContextManager(context).prepareTestInstance(testInstance);
099        }
100
101        /**
102         * Delegates to {@link TestContextManager#beforeTestMethod}.
103         */
104        @Override
105        public void beforeEach(ExtensionContext context) throws Exception {
106                Object testInstance = context.getRequiredTestInstance();
107                Method testMethod = context.getRequiredTestMethod();
108                getTestContextManager(context).beforeTestMethod(testInstance, testMethod);
109        }
110
111        /**
112         * Delegates to {@link TestContextManager#beforeTestExecution}.
113         */
114        @Override
115        public void beforeTestExecution(ExtensionContext context) throws Exception {
116                Object testInstance = context.getRequiredTestInstance();
117                Method testMethod = context.getRequiredTestMethod();
118                getTestContextManager(context).beforeTestExecution(testInstance, testMethod);
119        }
120
121        /**
122         * Delegates to {@link TestContextManager#afterTestExecution}.
123         */
124        @Override
125        public void afterTestExecution(ExtensionContext context) throws Exception {
126                Object testInstance = context.getRequiredTestInstance();
127                Method testMethod = context.getRequiredTestMethod();
128                Throwable testException = context.getExecutionException().orElse(null);
129                getTestContextManager(context).afterTestExecution(testInstance, testMethod, testException);
130        }
131
132        /**
133         * Delegates to {@link TestContextManager#afterTestMethod}.
134         */
135        @Override
136        public void afterEach(ExtensionContext context) throws Exception {
137                Object testInstance = context.getRequiredTestInstance();
138                Method testMethod = context.getRequiredTestMethod();
139                Throwable testException = context.getExecutionException().orElse(null);
140                getTestContextManager(context).afterTestMethod(testInstance, testMethod, testException);
141        }
142
143        /**
144         * Determine if the value for the {@link Parameter} in the supplied {@link ParameterContext}
145         * should be autowired from the test's {@link ApplicationContext}.
146         * <p>A parameter is considered to be autowirable if one of the following
147         * conditions is {@code true}.
148         * <ol>
149         * <li>The {@linkplain ParameterContext#getDeclaringExecutable() declaring
150         * executable} is a {@link Constructor} and
151         * {@link TestConstructorUtils#isAutowirableConstructor(Constructor, Class)}
152         * returns {@code true}.</li>
153         * <li>The parameter is of type {@link ApplicationContext} or a sub-type thereof.</li>
154         * <li>{@link ParameterResolutionDelegate#isAutowirable} returns {@code true}.</li>
155         * </ol>
156         * <p><strong>WARNING</strong>: If a test class {@code Constructor} is annotated
157         * with {@code @Autowired} or automatically autowirable (see {@link TestConstructor}),
158         * Spring will assume the responsibility for resolving all parameters in the
159         * constructor. Consequently, no other registered {@link ParameterResolver}
160         * will be able to resolve parameters.
161         * @see #resolveParameter
162         * @see TestConstructorUtils#isAutowirableConstructor(Constructor, Class)
163         * @see ParameterResolutionDelegate#isAutowirable
164         */
165        @Override
166        public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
167                Parameter parameter = parameterContext.getParameter();
168                Executable executable = parameter.getDeclaringExecutable();
169                Class<?> testClass = extensionContext.getRequiredTestClass();
170                return (TestConstructorUtils.isAutowirableConstructor(executable, testClass) ||
171                                ApplicationContext.class.isAssignableFrom(parameter.getType()) ||
172                                ParameterResolutionDelegate.isAutowirable(parameter, parameterContext.getIndex()));
173        }
174
175        /**
176         * Resolve a value for the {@link Parameter} in the supplied {@link ParameterContext} by
177         * retrieving the corresponding dependency from the test's {@link ApplicationContext}.
178         * <p>Delegates to {@link ParameterResolutionDelegate#resolveDependency}.
179         * @see #supportsParameter
180         * @see ParameterResolutionDelegate#resolveDependency
181         */
182        @Override
183        @Nullable
184        public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
185                Parameter parameter = parameterContext.getParameter();
186                int index = parameterContext.getIndex();
187                Class<?> testClass = extensionContext.getRequiredTestClass();
188                ApplicationContext applicationContext = getApplicationContext(extensionContext);
189                return ParameterResolutionDelegate.resolveDependency(parameter, index, testClass,
190                                applicationContext.getAutowireCapableBeanFactory());
191        }
192
193
194        /**
195         * Get the {@link ApplicationContext} associated with the supplied {@code ExtensionContext}.
196         * @param context the current {@code ExtensionContext} (never {@code null})
197         * @return the application context
198         * @throws IllegalStateException if an error occurs while retrieving the application context
199         * @see org.springframework.test.context.TestContext#getApplicationContext()
200         */
201        public static ApplicationContext getApplicationContext(ExtensionContext context) {
202                return getTestContextManager(context).getTestContext().getApplicationContext();
203        }
204
205        /**
206         * Get the {@link TestContextManager} associated with the supplied {@code ExtensionContext}.
207         * @return the {@code TestContextManager} (never {@code null})
208         */
209        private static TestContextManager getTestContextManager(ExtensionContext context) {
210                Assert.notNull(context, "ExtensionContext must not be null");
211                Class<?> testClass = context.getRequiredTestClass();
212                Store store = getStore(context);
213                return store.getOrComputeIfAbsent(testClass, TestContextManager::new, TestContextManager.class);
214        }
215
216        private static Store getStore(ExtensionContext context) {
217                return context.getRoot().getStore(NAMESPACE);
218        }
219
220}