001: /*
002: * Copyright 2006-2007, Unitils.org
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: * http://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: package org.unitils;
017:
018: import junit.framework.AssertionFailedError;
019: import junit.framework.TestCase;
020: import junit.framework.TestResult;
021: import org.apache.commons.lang.StringUtils;
022: import org.apache.commons.logging.Log;
023: import org.apache.commons.logging.LogFactory;
024: import org.unitils.core.TestListener;
025: import org.unitils.core.Unitils;
026: import org.unitils.core.UnitilsException;
027:
028: import java.lang.reflect.Method;
029:
030: /**
031: * Base test class that will Unitils-enable your test. This base class will make sure that the
032: * core unitils test listener methods are invoked in the expected order. See {@link TestListener} for
033: * more information on the listener invocation order.
034: *
035: * @author Tim Ducheyne
036: * @author Filip Neven
037: */
038: public abstract class UnitilsJUnit3 extends TestCase {
039:
040: /* The logger instance for this class */
041: private static Log logger = LogFactory.getLog(UnitilsJUnit3.class);
042:
043: /* The main test listener, that hooks this test into unitils */
044: private static TestListener testListener;
045:
046: /* True if beforeAll was succesfully called */
047: private static boolean beforeAllCalled;
048:
049: /* The class to which the last test belonged to */
050: private static Class<?> lastTestClass;
051:
052: /**
053: * Creates a test without a name. Be sure to call {@link TestCase#setName} afterwards.
054: */
055: public UnitilsJUnit3() {
056: this (null);
057: }
058:
059: /**
060: * Creates a test with the given name. The name should be the name of the test method.
061: *
062: * @param name the name of the test method
063: */
064: public UnitilsJUnit3(String name) {
065: super (name);
066:
067: if (testListener == null) {
068: testListener = getUnitils().createTestListener();
069: createShutdownHook();
070: }
071: }
072:
073: /**
074: * Overriden JUnit3 method to be able to call {@link TestListener#beforeAll}
075: *
076: * @param testResult junits test result, not null
077: */
078: @Override
079: public void run(TestResult testResult) {
080: try {
081: // if this the first test, call beforeAll
082: if (!beforeAllCalled) {
083: testListener.beforeAll();
084: beforeAllCalled = true;
085: }
086: } catch (AssertionFailedError e) {
087: testResult.addFailure(this , e);
088: testResult.stop(); // stop the test
089: return;
090:
091: } catch (Exception e) {
092: testResult.addError(this , e);
093: testResult.stop(); // stop the test
094: return;
095: }
096:
097: // run the test
098: super .run(testResult);
099: }
100:
101: /**
102: * Overriden JUnit3 method to be able to call {@link TestListener#beforeTestClass}, {@link TestListener#afterTestClass},
103: * {@link TestListener#beforeTestSetUp} and {@link TestListener#afterTestTearDown}.
104: * <p/>
105: * JUnit3 does not have a concept of class level hooks, such as BeforeClass and AfterClass in JUnit4. Therefore
106: * we need to simulate this behavior for unitils.
107: * When a test is about to be run that belongs to a new test class, we first call the afterTestClass of the previous class
108: * and the beforeTestClass of that new class. The last afterTestClass is called just before the afterAll,
109: * during the shutdown of the VM.
110: */
111: @Override
112: public void runBare() throws Throwable {
113: // simulate class level methods
114: // if this is the first test of a test class (previous test was of a different test class),
115: // first finalize the previous test class by calling afterTestClass, then call beforeTestClass
116: // to start the new one
117: Class<?> testClass = getClass();
118: if (lastTestClass != testClass) {
119: if (lastTestClass != null) {
120: try {
121: testListener.afterTestClass(lastTestClass);
122:
123: } catch (Throwable e) {
124: logger
125: .error(
126: "An exception occured during afterTestClass.",
127: e);
128: }
129: }
130: testListener.beforeTestClass(testClass);
131: lastTestClass = testClass;
132: }
133:
134: Throwable firstThrowable = null;
135: try {
136: testListener.beforeTestSetUp(this );
137: super .runBare();
138:
139: } catch (Throwable t) {
140: // hold exception until later, first call afterTestTearDown
141: firstThrowable = t;
142: }
143:
144: try {
145: testListener.afterTestTearDown(this );
146:
147: } catch (Throwable t) {
148: // first exception is typically the most meaningful, so ignore second exception
149: if (firstThrowable == null) {
150: firstThrowable = t;
151: }
152: }
153:
154: // if there were exceptions, throw the first one
155: if (firstThrowable != null) {
156: throw firstThrowable;
157: }
158: }
159:
160: /**
161: * Overriden JUnit3 method to be able to call {@link TestListener#beforeTestMethod} and
162: * {@link TestListener#afterTestMethod}.
163: */
164: @Override
165: protected void runTest() throws Throwable {
166: Throwable firstThrowable = null;
167: try {
168: testListener.beforeTestMethod(this , getCurrentTestMethod());
169: super .runTest();
170:
171: } catch (Throwable t) {
172: // hold exception until later, first call afterTestMethod
173: firstThrowable = t;
174: }
175:
176: try {
177: testListener.afterTestMethod(this , getCurrentTestMethod(),
178: firstThrowable);
179:
180: } catch (Throwable t) {
181: // first exception is typically the most meaningful, so ignore second exception
182: if (firstThrowable == null) {
183: firstThrowable = t;
184: }
185: }
186:
187: // if there were exceptions, throw the first one
188: if (firstThrowable != null) {
189: throw firstThrowable;
190: }
191: }
192:
193: /**
194: * This will return the default singleton instance by calling {@link Unitils#getInstance()}.
195: * <p/>
196: * You can override this method to let it create and set your own singleton instance. For example, you
197: * can let it create an instance of your own Unitils subclass and set it by using {@link Unitils#setInstance}.
198: *
199: * @return the unitils core instance, not null
200: */
201: protected Unitils getUnitils() {
202: return Unitils.getInstance();
203: }
204:
205: /**
206: * Gets the method that has the same name as the current test.
207: *
208: * @return the method, not null
209: * @throws UnitilsException if the method could not be found
210: */
211: protected Method getCurrentTestMethod() {
212: String testName = getName();
213: if (StringUtils.isEmpty(testName)) {
214: throw new UnitilsException(
215: "Unable to find current test method. No test name provided (null) for test. Test class: "
216: + getClass());
217: }
218:
219: try {
220: return getClass().getMethod(getName());
221:
222: } catch (NoSuchMethodException e) {
223: throw new UnitilsException(
224: "Unable to find current test method. Test name: "
225: + getName() + " , test class: "
226: + getClass(), e);
227: }
228: }
229:
230: /**
231: * Creates a hook that will call {@link TestListener#afterTestClass} of the last test class and
232: * {@link TestListener#afterAll} during the shutdown of the VM.
233: * <p/>
234: * This seems te be the only way in JUnit3 to this, since there is no way (without writing a custom test runner) to
235: * able to know when all test (of a class or in total) have run.
236: */
237: protected void createShutdownHook() {
238: Runtime.getRuntime().addShutdownHook(new Thread() {
239: @Override
240: public void run() {
241: if (testListener != null) {
242: if (lastTestClass != null) {
243: testListener.afterTestClass(lastTestClass);
244: }
245: testListener.afterAll();
246: }
247: }
248: });
249: }
250:
251: }
|