001: /*
002: * Copyright 2002-2007 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: * 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:
017: package org.springframework.test;
018:
019: import junit.framework.Assert;
020: import junit.framework.AssertionFailedError;
021:
022: /**
023: * Simple method object encapsulation of the 'test-for-Exception' scenario (for JUnit).
024: *
025: * <p>Used like so:
026: *
027: * <pre class="code">
028: * // the class under test
029: * public class Foo {
030: * public void someBusinessLogic(String name) {
031: * if (name == null) {
032: * throw new IllegalArgumentException("The 'name' argument is required");
033: * }
034: * // rest of business logic here...
035: * }
036: * }</pre>
037: *
038: * The test for the above bad argument path can be expressed using the
039: * {@link AssertThrows} class like so:
040: *
041: * <pre class="code">
042: * public class FooTest {
043: * public void testSomeBusinessLogicBadArgumentPath() {
044: * new AssertThrows(IllegalArgumentException.class) {
045: * public void test() {
046: * new Foo().someBusinessLogic(null);
047: * }
048: * }.runTest();
049: * }
050: * }</pre>
051: *
052: * This will result in the test passing if the <code>Foo.someBusinessLogic(..)</code>
053: * method threw an {@link java.lang.IllegalArgumentException}; if it did not, the
054: * test would fail with the following message:
055: *
056: * <pre class="code">
057: * "Must have thrown a [class java.lang.IllegalArgumentException]"</pre>
058: *
059: * If the <b>wrong</b> type of {@link java.lang.Exception} was thrown, the
060: * test will also fail, this time with a message similar to the following:
061: *
062: * <pre class="code">
063: * "junit.framework.AssertionFailedError: Was expecting a [class java.lang.UnsupportedOperationException] to be thrown, but instead a [class java.lang.IllegalArgumentException] was thrown"</pre>
064: *
065: * The test for the correct {@link java.lang.Exception} respects polymorphism,
066: * so you can test that any old {@link java.lang.Exception} is thrown like so:
067: *
068: * <pre class="code">
069: * public class FooTest {
070: * public void testSomeBusinessLogicBadArgumentPath() {
071: * // any Exception will do...
072: * new AssertThrows(Exception.class) {
073: * public void test() {
074: * new Foo().someBusinessLogic(null);
075: * }
076: * }.runTest();
077: * }
078: * }</pre>
079: *
080: * You might want to compare this class with the
081: * {@link junit.extensions.ExceptionTestCase} class.
082: *
083: * <p>Note: This class requires JDK 1.4 or higher.
084: *
085: * @author Rick Evans
086: * @author Juergen Hoeller
087: * @since 2.0
088: */
089: public abstract class AssertThrows {
090:
091: private final Class expectedException;
092:
093: private String failureMessage;
094:
095: private Exception actualException;
096:
097: /**
098: * Create a new instance of the {@link AssertThrows} class.
099: * @param expectedException the {@link java.lang.Exception} expected to be
100: * thrown during the execution of the surrounding test
101: * @throws IllegalArgumentException if the supplied <code>expectedException</code> is
102: * <code>null</code>; or if said argument is not an {@link java.lang.Exception}-derived class
103: */
104: public AssertThrows(Class expectedException) {
105: this (expectedException, null);
106: }
107:
108: /**
109: * Create a new instance of the {@link AssertThrows} class.
110: * @param expectedException the {@link java.lang.Exception} expected to be
111: * thrown during the execution of the surrounding test
112: * @param failureMessage the extra, contextual failure message that will be
113: * included in the failure text if the text fails (can be <code>null</code>)
114: * @throws IllegalArgumentException if the supplied <code>expectedException</code> is
115: * <code>null</code>; or if said argument is not an {@link java.lang.Exception}-derived class
116: */
117: public AssertThrows(Class expectedException, String failureMessage) {
118: if (expectedException == null) {
119: throw new IllegalArgumentException(
120: "The 'expectedException' argument is required");
121: }
122: if (!Exception.class.isAssignableFrom(expectedException)) {
123: throw new IllegalArgumentException(
124: "The 'expectedException' argument is not an Exception type (it obviously must be)");
125: }
126: this .expectedException = expectedException;
127: this .failureMessage = failureMessage;
128: }
129:
130: /**
131: * Return the {@link java.lang.Exception} expected to be thrown during
132: * the execution of the surrounding test.
133: */
134: protected Class getExpectedException() {
135: return this .expectedException;
136: }
137:
138: /**
139: * Set the extra, contextual failure message that will be included
140: * in the failure text if the text fails.
141: */
142: public void setFailureMessage(String failureMessage) {
143: this .failureMessage = failureMessage;
144: }
145:
146: /**
147: * Return the extra, contextual failure message that will be included
148: * in the failure text if the text fails.
149: */
150: protected String getFailureMessage() {
151: return this .failureMessage;
152: }
153:
154: /**
155: * Subclass must override this <code>abstract</code> method and
156: * provide the test logic.
157: * @throws Exception if an error occurs during the execution of the
158: * aformentioned test logic
159: */
160: public abstract void test() throws Exception;
161:
162: /**
163: * The main template method that drives the running of the
164: * {@link #test() test logic} and the
165: * {@link #checkExceptionExpectations(Exception) checking} of the
166: * resulting (expected) {@link java.lang.Exception}.
167: * @see #test()
168: * @see #doFail()
169: * @see #checkExceptionExpectations(Exception)
170: */
171: public void runTest() {
172: try {
173: test();
174: doFail();
175: } catch (Exception actualException) {
176: this .actualException = actualException;
177: checkExceptionExpectations(actualException);
178: }
179: }
180:
181: /**
182: * Template method called when the test fails; i.e. the expected
183: * {@link java.lang.Exception} is <b>not</b> thrown.
184: * <p>The default implementation simply fails the test via a call to
185: * {@link junit.framework.Assert#fail(String)}.
186: * <p>If you want to customise the failure message, consider overriding
187: * {@link #createMessageForNoExceptionThrown()}, and / or supplying an
188: * extra, contextual failure message via the appropriate constructor overload.
189: * @see #getFailureMessage()
190: */
191: protected void doFail() {
192: Assert.fail(createMessageForNoExceptionThrown());
193: }
194:
195: /**
196: * Creates the failure message used if the test fails
197: * (i.e. the expected exception is not thrown in the body of the test).
198: * @return the failure message used if the test fails
199: * @see #getFailureMessage()
200: */
201: protected String createMessageForNoExceptionThrown() {
202: StringBuffer sb = new StringBuffer();
203: sb.append("Should have thrown a [").append(
204: this .getExpectedException()).append("]");
205: if (getFailureMessage() != null) {
206: sb.append(": ").append(getFailureMessage());
207: }
208: return sb.toString();
209: }
210:
211: /**
212: * Does the donkey work of checking (verifying) that the
213: * {@link java.lang.Exception} that was thrown in the body of a test is
214: * an instance of the {@link #getExpectedException()} class (or an
215: * instance of a subclass).
216: * <p>If you want to customise the failure message, consider overriding
217: * {@link #createMessageForWrongThrownExceptionType(Exception)}.
218: * @param actualException the {@link java.lang.Exception} that has been thrown
219: * in the body of a test method (will never be <code>null</code>)
220: */
221: protected void checkExceptionExpectations(Exception actualException) {
222: if (!getExpectedException().isAssignableFrom(
223: actualException.getClass())) {
224: AssertionFailedError error = new AssertionFailedError(
225: createMessageForWrongThrownExceptionType(actualException));
226: error.initCause(actualException);
227: throw error;
228: }
229: }
230:
231: /**
232: * Creates the failure message used if the wrong type
233: * of {@link java.lang.Exception} is thrown in the body of the test.
234: * @param actualException the actual exception thrown
235: * @return the message for the given exception
236: */
237: protected String createMessageForWrongThrownExceptionType(
238: Exception actualException) {
239: StringBuffer sb = new StringBuffer();
240: sb.append("Was expecting a [").append(
241: getExpectedException().getName());
242: sb.append("] to be thrown, but instead a [").append(
243: actualException.getClass().getName());
244: sb.append("] was thrown.");
245: return sb.toString();
246: }
247:
248: /**
249: * Expose the actual exception thrown from {@link #test}, if any.
250: * @return the actual exception, or <code>null</code> if none
251: */
252: public final Exception getActualException() {
253: return this.actualException;
254: }
255:
256: }
|