001package org.junit.rules;
002
003import static org.hamcrest.CoreMatchers.containsString;
004import static org.hamcrest.CoreMatchers.instanceOf;
005import static org.junit.Assert.assertThat;
006import static org.junit.Assert.fail;
007import static org.junit.internal.matchers.ThrowableCauseMatcher.hasCause;
008import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage;
009
010import org.hamcrest.Matcher;
011import org.hamcrest.StringDescription;
012import org.junit.internal.AssumptionViolatedException;
013import org.junit.runners.model.Statement;
014
015/**
016 * The ExpectedException rule allows in-test specification of expected exception
017 * types and messages:
018 *
019 * <pre>
020 * // These tests all pass.
021 * public static class HasExpectedException {
022 *      &#064;Rule
023 *      public ExpectedException thrown= ExpectedException.none();
024 *
025 *      &#064;Test
026 *      public void throwsNothing() {
027 *              // no exception expected, none thrown: passes.
028 *     }
029 *
030 *      &#064;Test
031 *      public void throwsNullPointerException() {
032 *              thrown.expect(NullPointerException.class);
033 *              throw new NullPointerException();
034 *     }
035 *
036 *      &#064;Test
037 *      public void throwsNullPointerExceptionWithMessage() {
038 *              thrown.expect(NullPointerException.class);
039 *              thrown.expectMessage(&quot;happened?&quot;);
040 *              thrown.expectMessage(startsWith(&quot;What&quot;));
041 *              throw new NullPointerException(&quot;What happened?&quot;);
042 *     }
043 *
044 *      &#064;Test
045 *      public void throwsIllegalArgumentExceptionWithMessageAndCause() {
046 *              NullPointerException expectedCause = new NullPointerException();
047 *              thrown.expect(IllegalArgumentException.class);
048 *              thrown.expectMessage(&quot;What&quot;);
049 *              thrown.expectCause(is(expectedCause));
050 *              throw new IllegalArgumentException(&quot;What happened?&quot;, cause);
051 *     }
052 * }
053 * </pre>
054 *
055 * By default ExpectedException rule doesn't handle AssertionErrors and
056 * AssumptionViolatedExceptions, because such exceptions are used by JUnit. If
057 * you want to handle such exceptions you have to call @link
058 * {@link #handleAssertionErrors()} or @link
059 * {@link #handleAssumptionViolatedExceptions()}.
060 *
061 * <pre>
062 * // These tests all pass.
063 * public static class HasExpectedException {
064 *      &#064;Rule
065 *      public ExpectedException thrown= ExpectedException.none();
066 *
067 *      &#064;Test
068 *      public void throwExpectedAssertionError() {
069 *              thrown.handleAssertionErrors();
070 *              thrown.expect(AssertionError.class);
071 *              throw new AssertionError();
072 *     }
073 *
074 *  &#064;Test
075 *  public void throwExpectAssumptionViolatedException() {
076 *      thrown.handleAssumptionViolatedExceptions();
077 *      thrown.expect(AssumptionViolatedException.class);
078 *      throw new AssumptionViolatedException(&quot;&quot;);
079 *     }
080 * }
081 * </pre>
082 *
083 * @since 4.7
084 */
085public class ExpectedException implements TestRule {
086    /**
087     * @return a Rule that expects no exception to be thrown (identical to
088     *         behavior without this Rule)
089     */
090    public static ExpectedException none() {
091        return new ExpectedException();
092    }
093
094    private final ExpectedExceptionMatcherBuilder fMatcherBuilder = new ExpectedExceptionMatcherBuilder();
095
096    private boolean handleAssumptionViolatedExceptions = false;
097
098    private boolean handleAssertionErrors = false;
099    
100    private String missingExceptionMessage;
101
102    private ExpectedException() {
103    }
104
105    public ExpectedException handleAssertionErrors() {
106        handleAssertionErrors = true;
107        return this;
108    }
109
110    public ExpectedException handleAssumptionViolatedExceptions() {
111        handleAssumptionViolatedExceptions = true;
112        return this;
113    }
114    
115    /**
116     * Specifies the failure message for tests that are expected to throw 
117     * an exception but do not throw any.
118     * @param message exception detail message
119     * @return self
120     */
121    public ExpectedException reportMissingExceptionWithMessage(String message) {
122        missingExceptionMessage = message;
123        return this;
124    }
125
126    public Statement apply(Statement base,
127            org.junit.runner.Description description) {
128        return new ExpectedExceptionStatement(base);
129    }
130
131    /**
132     * Adds {@code matcher} to the list of requirements for any thrown
133     * exception.
134     */
135    public void expect(Matcher<?> matcher) {
136        fMatcherBuilder.add(matcher);
137    }
138
139    /**
140     * Adds to the list of requirements for any thrown exception that it should
141     * be an instance of {@code type}
142     */
143    public void expect(Class<? extends Throwable> type) {
144        expect(instanceOf(type));
145    }
146
147    /**
148     * Adds to the list of requirements for any thrown exception that it should
149     * <em>contain</em> string {@code substring}
150     */
151    public void expectMessage(String substring) {
152        expectMessage(containsString(substring));
153    }
154
155    /**
156     * Adds {@code matcher} to the list of requirements for the message returned
157     * from any thrown exception.
158     */
159    public void expectMessage(Matcher<String> matcher) {
160        expect(hasMessage(matcher));
161    }
162
163    /**
164     * Adds {@code matcher} to the list of requirements for the cause of
165     * any thrown exception.
166     */
167    public void expectCause(Matcher<? extends Throwable> expectedCause) {
168        expect(hasCause(expectedCause));
169    }
170
171    private class ExpectedExceptionStatement extends Statement {
172        private final Statement fNext;
173
174        public ExpectedExceptionStatement(Statement base) {
175            fNext = base;
176        }
177
178        @Override
179        public void evaluate() throws Throwable {
180            try {
181                fNext.evaluate();
182                if (fMatcherBuilder.expectsThrowable()) {
183                    failDueToMissingException();
184                }
185            } catch (AssumptionViolatedException e) {
186                optionallyHandleException(e, handleAssumptionViolatedExceptions);
187            } catch (AssertionError e) {
188                optionallyHandleException(e, handleAssertionErrors);
189            } catch (Throwable e) {
190                handleException(e);
191            }
192        }
193    }
194
195    private void failDueToMissingException() throws AssertionError {
196        fail(missingExceptionMessage());
197    }
198
199    private void optionallyHandleException(Throwable e, boolean handleException)
200            throws Throwable {
201        if (handleException) {
202            handleException(e);
203        } else {
204            throw e;
205        }
206    }
207
208    private void handleException(Throwable e) throws Throwable {
209        if (fMatcherBuilder.expectsThrowable()) {
210            assertThat(e, fMatcherBuilder.build());
211        } else {
212            throw e;
213        }
214    }
215    
216    private String missingExceptionMessage() {
217        if (isMissingExceptionMessageEmpty()) {
218            String expectation = StringDescription.toString(fMatcherBuilder.build());
219            return "Expected test to throw " + expectation;
220        } else {
221            return missingExceptionMessage;
222        }        
223    }
224    
225    private boolean isMissingExceptionMessageEmpty() {
226        return missingExceptionMessage == null || missingExceptionMessage.length() == 0;
227    }
228}