diff options
Diffstat (limited to 'src/main/java/org/junit/rules/ExpectedException.java')
-rw-r--r-- | src/main/java/org/junit/rules/ExpectedException.java | 382 |
1 files changed, 258 insertions, 124 deletions
diff --git a/src/main/java/org/junit/rules/ExpectedException.java b/src/main/java/org/junit/rules/ExpectedException.java index bac2fba..4d61712 100644 --- a/src/main/java/org/junit/rules/ExpectedException.java +++ b/src/main/java/org/junit/rules/ExpectedException.java @@ -1,136 +1,270 @@ package org.junit.rules; +import static java.lang.String.format; +import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.instanceOf; -import static org.junit.matchers.JUnitMatchers.both; -import static org.junit.matchers.JUnitMatchers.containsString; -import org.hamcrest.Description; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.junit.internal.matchers.ThrowableCauseMatcher.hasCause; +import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage; + import org.hamcrest.Matcher; import org.hamcrest.StringDescription; -import org.junit.Assert; -import org.junit.internal.matchers.TypeSafeMatcher; +import org.junit.AssumptionViolatedException; import org.junit.runners.model.Statement; /** - * The ExpectedException Rule allows in-test specification of expected exception - * types and messages: - * - * <pre> - * // These tests all pass. - * public static class HasExpectedException { - * @Rule - * public ExpectedException thrown= ExpectedException.none(); - * - * @Test - * public void throwsNothing() { - * // no exception expected, none thrown: passes. - * } + * The {@code ExpectedException} rule allows you to verify that your code + * throws a specific exception. + * + * <h3>Usage</h3> + * + * <pre> public class SimpleExpectedExceptionTest { + * @Rule + * public ExpectedException thrown= ExpectedException.none(); + * + * @Test + * public void throwsNothing() { + * // no exception expected, none thrown: passes. + * } + * + * @Test + * public void throwsExceptionWithSpecificType() { + * thrown.expect(NullPointerException.class); + * throw new NullPointerException(); + * } + * }</pre> * - * @Test - * public void throwsNullPointerException() { - * thrown.expect(NullPointerException.class); - * throw new NullPointerException(); - * } - * - * @Test - * public void throwsNullPointerExceptionWithMessage() { - * thrown.expect(NullPointerException.class); - * thrown.expectMessage("happened?"); - * thrown.expectMessage(startsWith("What")); - * throw new NullPointerException("What happened?"); - * } - * } - * </pre> + * <p> + * You have to add the {@code ExpectedException} rule to your test. + * This doesn't affect your existing tests (see {@code throwsNothing()}). + * After specifiying the type of the expected exception your test is + * successful when such an exception is thrown and it fails if a + * different or no exception is thrown. + * + * <p> + * Instead of specifying the exception's type you can characterize the + * expected exception based on other criterias, too: + * + * <ul> + * <li>The exception's message contains a specific text: {@link #expectMessage(String)}</li> + * <li>The exception's message complies with a Hamcrest matcher: {@link #expectMessage(Matcher)}</li> + * <li>The exception's cause complies with a Hamcrest matcher: {@link #expectCause(Matcher)}</li> + * <li>The exception itself complies with a Hamcrest matcher: {@link #expect(Matcher)}</li> + * </ul> + * + * <p> + * You can combine any of the presented expect-methods. The test is + * successful if all specifications are met. + * <pre> @Test + * public void throwsException() { + * thrown.expect(NullPointerException.class); + * thrown.expectMessage("happened"); + * throw new NullPointerException("What happened?"); + * }</pre> + * + * <h3>AssumptionViolatedExceptions</h3> + * <p> + * JUnit uses {@link AssumptionViolatedException}s for indicating that a test + * provides no useful information. (See {@link org.junit.Assume} for more + * information.) You have to call {@code assume} methods before you set + * expectations of the {@code ExpectedException} rule. In this case the rule + * will not handle consume the exceptions and it can be handled by the + * framework. E.g. the following test is ignored by JUnit's default runner. + * + * <pre> @Test + * public void ignoredBecauseOfFailedAssumption() { + * assumeTrue(false); // throws AssumptionViolatedException + * thrown.expect(NullPointerException.class); + * }</pre> + * + * <h3>AssertionErrors</h3> + * + * <p> + * JUnit uses {@link AssertionError}s for indicating that a test is failing. You + * have to call {@code assert} methods before you set expectations of the + * {@code ExpectedException} rule, if they should be handled by the framework. + * E.g. the following test fails because of the {@code assertTrue} statement. + * + * <pre> @Test + * public void throwsUnhandled() { + * assertTrue(false); // throws AssertionError + * thrown.expect(NullPointerException.class); + * }</pre> + * + * <h3>Missing Exceptions</h3> + * <p> + * By default missing exceptions are reported with an error message + * like "Expected test to throw an instance of foo". You can configure a different + * message by means of {@link #reportMissingExceptionWithMessage(String)}. You + * can use a {@code %s} placeholder for the description of the expected + * exception. E.g. "Test doesn't throw %s." will fail with the error message + * "Test doesn't throw an instance of foo.". + * + * @since 4.7 */ public class ExpectedException implements TestRule { - /** - * @return a Rule that expects no exception to be thrown - * (identical to behavior without this Rule) - */ - public static ExpectedException none() { - return new ExpectedException(); - } - - private Matcher<Object> fMatcher= null; - - private ExpectedException() { - - } - - public Statement apply(Statement base, - org.junit.runner.Description description) { - return new ExpectedExceptionStatement(base); - } - - /** - * Adds {@code matcher} to the list of requirements for any thrown exception. - */ - // Should be able to remove this suppression in some brave new hamcrest world. - @SuppressWarnings("unchecked") - public void expect(Matcher<?> matcher) { - if (fMatcher == null) - fMatcher= (Matcher<Object>) matcher; - else - fMatcher= both(fMatcher).and(matcher); - } - - /** - * Adds to the list of requirements for any thrown exception that it - * should be an instance of {@code type} - */ - public void expect(Class<? extends Throwable> type) { - expect(instanceOf(type)); - } - - /** - * Adds to the list of requirements for any thrown exception that it - * should <em>contain</em> string {@code substring} - */ - public void expectMessage(String substring) { - expectMessage(containsString(substring)); - } - - /** - * Adds {@code matcher} to the list of requirements for the message - * returned from any thrown exception. - */ - public void expectMessage(Matcher<String> matcher) { - expect(hasMessage(matcher)); - } - - private class ExpectedExceptionStatement extends Statement { - private final Statement fNext; - - public ExpectedExceptionStatement(Statement base) { - fNext= base; - } - - @Override - public void evaluate() throws Throwable { - try { - fNext.evaluate(); - } catch (Throwable e) { - if (fMatcher == null) - throw e; - Assert.assertThat(e, fMatcher); - return; - } - if (fMatcher != null) - throw new AssertionError("Expected test to throw " - + StringDescription.toString(fMatcher)); - } - } - - private Matcher<Throwable> hasMessage(final Matcher<String> matcher) { - return new TypeSafeMatcher<Throwable>() { - public void describeTo(Description description) { - description.appendText("exception with message "); - description.appendDescriptionOf(matcher); - } - - @Override - public boolean matchesSafely(Throwable item) { - return matcher.matches(item.getMessage()); - } - }; - } + /** + * Returns a {@linkplain TestRule rule} that expects no exception to + * be thrown (identical to behavior without this rule). + */ + public static ExpectedException none() { + return new ExpectedException(); + } + + private final ExpectedExceptionMatcherBuilder matcherBuilder = new ExpectedExceptionMatcherBuilder(); + + private String missingExceptionMessage= "Expected test to throw %s"; + + private ExpectedException() { + } + + /** + * This method does nothing. Don't use it. + * @deprecated AssertionErrors are handled by default since JUnit 4.12. Just + * like in JUnit <= 4.10. + */ + @Deprecated + public ExpectedException handleAssertionErrors() { + return this; + } + + /** + * This method does nothing. Don't use it. + * @deprecated AssumptionViolatedExceptions are handled by default since + * JUnit 4.12. Just like in JUnit <= 4.10. + */ + @Deprecated + public ExpectedException handleAssumptionViolatedExceptions() { + return this; + } + + /** + * Specifies the failure message for tests that are expected to throw + * an exception but do not throw any. You can use a {@code %s} placeholder for + * the description of the expected exception. E.g. "Test doesn't throw %s." + * will fail with the error message + * "Test doesn't throw an instance of foo.". + * + * @param message exception detail message + * @return the rule itself + */ + public ExpectedException reportMissingExceptionWithMessage(String message) { + missingExceptionMessage = message; + return this; + } + + public Statement apply(Statement base, + org.junit.runner.Description description) { + return new ExpectedExceptionStatement(base); + } + + /** + * Verify that your code throws an exception that is matched by + * a Hamcrest matcher. + * <pre> @Test + * public void throwsExceptionThatCompliesWithMatcher() { + * NullPointerException e = new NullPointerException(); + * thrown.expect(is(e)); + * throw e; + * }</pre> + */ + public void expect(Matcher<?> matcher) { + matcherBuilder.add(matcher); + } + + /** + * Verify that your code throws an exception that is an + * instance of specific {@code type}. + * <pre> @Test + * public void throwsExceptionWithSpecificType() { + * thrown.expect(NullPointerException.class); + * throw new NullPointerException(); + * }</pre> + */ + public void expect(Class<? extends Throwable> type) { + expect(instanceOf(type)); + } + + /** + * Verify that your code throws an exception whose message contains + * a specific text. + * <pre> @Test + * public void throwsExceptionWhoseMessageContainsSpecificText() { + * thrown.expectMessage("happened"); + * throw new NullPointerException("What happened?"); + * }</pre> + */ + public void expectMessage(String substring) { + expectMessage(containsString(substring)); + } + + /** + * Verify that your code throws an exception whose message is matched + * by a Hamcrest matcher. + * <pre> @Test + * public void throwsExceptionWhoseMessageCompliesWithMatcher() { + * thrown.expectMessage(startsWith("What")); + * throw new NullPointerException("What happened?"); + * }</pre> + */ + public void expectMessage(Matcher<String> matcher) { + expect(hasMessage(matcher)); + } + + /** + * Verify that your code throws an exception whose cause is matched by + * a Hamcrest matcher. + * <pre> @Test + * public void throwsExceptionWhoseCauseCompliesWithMatcher() { + * NullPointerException expectedCause = new NullPointerException(); + * thrown.expectCause(is(expectedCause)); + * throw new IllegalArgumentException("What happened?", cause); + * }</pre> + */ + public void expectCause(Matcher<? extends Throwable> expectedCause) { + expect(hasCause(expectedCause)); + } + + private class ExpectedExceptionStatement extends Statement { + private final Statement next; + + public ExpectedExceptionStatement(Statement base) { + next = base; + } + + @Override + public void evaluate() throws Throwable { + try { + next.evaluate(); + } catch (Throwable e) { + handleException(e); + return; + } + if (isAnyExceptionExpected()) { + failDueToMissingException(); + } + } + } + + private void handleException(Throwable e) throws Throwable { + if (isAnyExceptionExpected()) { + assertThat(e, matcherBuilder.build()); + } else { + throw e; + } + } + + private boolean isAnyExceptionExpected() { + return matcherBuilder.expectsThrowable(); + } + + private void failDueToMissingException() throws AssertionError { + fail(missingExceptionMessage()); + } + + private String missingExceptionMessage() { + String expectation= StringDescription.toString(matcherBuilder.build()); + return format(missingExceptionMessage, expectation); + } } |