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.testng;
018
019import java.lang.reflect.InvocationTargetException;
020import java.lang.reflect.Method;
021
022import org.apache.commons.logging.Log;
023import org.apache.commons.logging.LogFactory;
024import org.testng.IHookCallBack;
025import org.testng.IHookable;
026import org.testng.ITestResult;
027import org.testng.annotations.AfterClass;
028import org.testng.annotations.AfterMethod;
029import org.testng.annotations.BeforeClass;
030import org.testng.annotations.BeforeMethod;
031
032import org.springframework.context.ApplicationContext;
033import org.springframework.context.ApplicationContextAware;
034import org.springframework.lang.Nullable;
035import org.springframework.test.context.ContextConfiguration;
036import org.springframework.test.context.TestContext;
037import org.springframework.test.context.TestContextManager;
038import org.springframework.test.context.TestExecutionListeners;
039import org.springframework.test.context.event.EventPublishingTestExecutionListener;
040import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
041import org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener;
042import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
043import org.springframework.test.context.web.ServletTestExecutionListener;
044
045/**
046 * Abstract base test class which integrates the <em>Spring TestContext Framework</em>
047 * with explicit {@link ApplicationContext} testing support in a <strong>TestNG</strong>
048 * environment.
049 *
050 * <p>Concrete subclasses should typically declare a class-level
051 * {@link ContextConfiguration @ContextConfiguration} annotation to
052 * configure the {@linkplain ApplicationContext application context} {@linkplain
053 * ContextConfiguration#locations() resource locations} or {@linkplain
054 * ContextConfiguration#classes() component classes}. <em>If your test does not
055 * need to load an application context, you may choose to omit the
056 * {@link ContextConfiguration @ContextConfiguration} declaration and to configure
057 * the appropriate {@link org.springframework.test.context.TestExecutionListener
058 * TestExecutionListeners} manually.</em> Concrete subclasses must also have
059 * constructors which either implicitly or explicitly delegate to {@code super();}.
060 *
061 * <p>The following {@link org.springframework.test.context.TestExecutionListener
062 * TestExecutionListeners} are configured by default:
063 *
064 * <ul>
065 * <li>{@link org.springframework.test.context.web.ServletTestExecutionListener}
066 * <li>{@link org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener}
067 * <li>{@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener}
068 * <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener}
069 * <li>{@link org.springframework.test.context.event.EventPublishingTestExecutionListener}
070 * </ul>
071 *
072 * @author Sam Brannen
073 * @author Juergen Hoeller
074 * @since 2.5
075 * @see ContextConfiguration
076 * @see TestContext
077 * @see TestContextManager
078 * @see TestExecutionListeners
079 * @see ServletTestExecutionListener
080 * @see DirtiesContextBeforeModesTestExecutionListener
081 * @see DependencyInjectionTestExecutionListener
082 * @see DirtiesContextTestExecutionListener
083 * @see EventPublishingTestExecutionListener
084 * @see AbstractTransactionalTestNGSpringContextTests
085 * @see org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests
086 */
087@TestExecutionListeners({ ServletTestExecutionListener.class, DirtiesContextBeforeModesTestExecutionListener.class,
088        DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class,
089        EventPublishingTestExecutionListener.class })
090public abstract class AbstractTestNGSpringContextTests implements IHookable, ApplicationContextAware {
091
092        /** Logger available to subclasses. */
093        protected final Log logger = LogFactory.getLog(getClass());
094
095        /**
096         * The {@link ApplicationContext} that was injected into this test instance
097         * via {@link #setApplicationContext(ApplicationContext)}.
098         */
099        @Nullable
100        protected ApplicationContext applicationContext;
101
102        private final TestContextManager testContextManager;
103
104        @Nullable
105        private Throwable testException;
106
107
108        /**
109         * Construct a new {@code AbstractTestNGSpringContextTests} instance and initialize
110         * the internal {@link TestContextManager} for the current test class.
111         */
112        public AbstractTestNGSpringContextTests() {
113                this.testContextManager = new TestContextManager(getClass());
114        }
115
116        /**
117         * Set the {@link ApplicationContext} to be used by this test instance,
118         * provided via {@link ApplicationContextAware} semantics.
119         * @param applicationContext the ApplicationContext that this test runs in
120         */
121        @Override
122        public final void setApplicationContext(ApplicationContext applicationContext) {
123                this.applicationContext = applicationContext;
124        }
125
126
127        /**
128         * Delegates to the configured {@link TestContextManager} to call
129         * {@linkplain TestContextManager#beforeTestClass() 'before test class'} callbacks.
130         * @throws Exception if a registered TestExecutionListener throws an exception
131         */
132        @BeforeClass(alwaysRun = true)
133        protected void springTestContextBeforeTestClass() throws Exception {
134                this.testContextManager.beforeTestClass();
135        }
136
137        /**
138         * Delegates to the configured {@link TestContextManager} to
139         * {@linkplain TestContextManager#prepareTestInstance(Object) prepare} this test
140         * instance prior to execution of any individual tests, for example for
141         * injecting dependencies, etc.
142         * @throws Exception if a registered TestExecutionListener throws an exception
143         */
144        @BeforeClass(alwaysRun = true, dependsOnMethods = "springTestContextBeforeTestClass")
145        protected void springTestContextPrepareTestInstance() throws Exception {
146                this.testContextManager.prepareTestInstance(this);
147        }
148
149        /**
150         * Delegates to the configured {@link TestContextManager} to
151         * {@linkplain TestContextManager#beforeTestMethod(Object,Method) pre-process}
152         * the test method before the actual test is executed.
153         * @param testMethod the test method which is about to be executed
154         * @throws Exception allows all exceptions to propagate
155         */
156        @BeforeMethod(alwaysRun = true)
157        protected void springTestContextBeforeTestMethod(Method testMethod) throws Exception {
158                this.testContextManager.beforeTestMethod(this, testMethod);
159        }
160
161        /**
162         * Delegates to the {@linkplain IHookCallBack#runTestMethod(ITestResult) test
163         * method} in the supplied {@code callback} to execute the actual test
164         * and then tracks the exception thrown during test execution, if any.
165         * @see org.testng.IHookable#run(IHookCallBack, ITestResult)
166         */
167        @Override
168        public void run(IHookCallBack callBack, ITestResult testResult) {
169                Method testMethod = testResult.getMethod().getConstructorOrMethod().getMethod();
170                boolean beforeCallbacksExecuted = false;
171
172                try {
173                        this.testContextManager.beforeTestExecution(this, testMethod);
174                        beforeCallbacksExecuted = true;
175                }
176                catch (Throwable ex) {
177                        this.testException = ex;
178                }
179
180                if (beforeCallbacksExecuted) {
181                        callBack.runTestMethod(testResult);
182                        this.testException = getTestResultException(testResult);
183                }
184
185                try {
186                        this.testContextManager.afterTestExecution(this, testMethod, this.testException);
187                }
188                catch (Throwable ex) {
189                        if (this.testException == null) {
190                                this.testException = ex;
191                        }
192                }
193
194                if (this.testException != null) {
195                        throwAsUncheckedException(this.testException);
196                }
197        }
198
199        /**
200         * Delegates to the configured {@link TestContextManager} to
201         * {@linkplain TestContextManager#afterTestMethod(Object, Method, Throwable)
202         * post-process} the test method after the actual test has executed.
203         *
204         * @param testMethod the test method which has just been executed on the
205         * test instance
206         * @throws Exception allows all exceptions to propagate
207         */
208        @AfterMethod(alwaysRun = true)
209        protected void springTestContextAfterTestMethod(Method testMethod) throws Exception {
210                try {
211                        this.testContextManager.afterTestMethod(this, testMethod, this.testException);
212                }
213                finally {
214                        this.testException = null;
215                }
216        }
217
218        /**
219         * Delegates to the configured {@link TestContextManager} to call
220         * {@linkplain TestContextManager#afterTestClass() 'after test class'} callbacks.
221         * @throws Exception if a registered TestExecutionListener throws an exception
222         */
223        @AfterClass(alwaysRun = true)
224        protected void springTestContextAfterTestClass() throws Exception {
225                this.testContextManager.afterTestClass();
226        }
227
228
229        private Throwable getTestResultException(ITestResult testResult) {
230                Throwable testResultException = testResult.getThrowable();
231                if (testResultException instanceof InvocationTargetException) {
232                        testResultException = ((InvocationTargetException) testResultException).getCause();
233                }
234                return testResultException;
235        }
236
237        private RuntimeException throwAsUncheckedException(Throwable t) {
238                throwAs(t);
239                // Appeasing the compiler: the following line will never be executed.
240                throw new IllegalStateException(t);
241        }
242
243        @SuppressWarnings("unchecked")
244        private <T extends Throwable> void throwAs(Throwable t) throws T {
245                throw (T) t;
246        }
247
248}