aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/junit/rules/ExpectedException.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/junit/rules/ExpectedException.java')
-rw-r--r--src/main/java/org/junit/rules/ExpectedException.java382
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 {
- * &#064;Rule
- * public ExpectedException thrown= ExpectedException.none();
- *
- * &#064;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 {
+ * &#064;Rule
+ * public ExpectedException thrown= ExpectedException.none();
+ *
+ * &#064;Test
+ * public void throwsNothing() {
+ * // no exception expected, none thrown: passes.
+ * }
+ *
+ * &#064;Test
+ * public void throwsExceptionWithSpecificType() {
+ * thrown.expect(NullPointerException.class);
+ * throw new NullPointerException();
+ * }
+ * }</pre>
*
- * &#064;Test
- * public void throwsNullPointerException() {
- * thrown.expect(NullPointerException.class);
- * throw new NullPointerException();
- * }
- *
- * &#064;Test
- * public void throwsNullPointerExceptionWithMessage() {
- * thrown.expect(NullPointerException.class);
- * thrown.expectMessage(&quot;happened?&quot;);
- * thrown.expectMessage(startsWith(&quot;What&quot;));
- * throw new NullPointerException(&quot;What happened?&quot;);
- * }
- * }
- * </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> &#064;Test
+ * public void throwsException() {
+ * thrown.expect(NullPointerException.class);
+ * thrown.expectMessage(&quot;happened&quot;);
+ * throw new NullPointerException(&quot;What happened?&quot;);
+ * }</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> &#064;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> &#064;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 &lt;= 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 &lt;= 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> &#064;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> &#064;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> &#064;Test
+ * public void throwsExceptionWhoseMessageContainsSpecificText() {
+ * thrown.expectMessage(&quot;happened&quot;);
+ * throw new NullPointerException(&quot;What happened?&quot;);
+ * }</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> &#064;Test
+ * public void throwsExceptionWhoseMessageCompliesWithMatcher() {
+ * thrown.expectMessage(startsWith(&quot;What&quot;));
+ * throw new NullPointerException(&quot;What happened?&quot;);
+ * }</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> &#064;Test
+ * public void throwsExceptionWhoseCauseCompliesWithMatcher() {
+ * NullPointerException expectedCause = new NullPointerException();
+ * thrown.expectCause(is(expectedCause));
+ * throw new IllegalArgumentException(&quot;What happened?&quot;, 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);
+ }
}