Skip to content

Commit

Permalink
Merge pull request #720 from stefanbirkner/expected-exception
Browse files Browse the repository at this point in the history
Extend ExpectedException's documentation and fix #687.
  • Loading branch information
David Saff committed Oct 16, 2013
2 parents 5e6cb8d + 8b35f0c commit 0f623cd
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 168 deletions.
224 changes: 131 additions & 93 deletions src/main/java/org/junit/rules/ExpectedException.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,117 +6,139 @@
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.internal.AssumptionViolatedException;
import org.junit.runners.model.Statement;

/**
* The ExpectedException rule allows in-test specification of expected exception
* types and messages:
* The {@code ExpectedException} rule allows you to verify that your code
* throws a specific exception.
*
* <pre>
* // These tests all pass.
* public static class HasExpectedException {
* &#064;Rule
* public ExpectedException thrown= ExpectedException.none();
* <h3>Usage</h3>
*
* &#064;Test
* public void throwsNothing() {
* // no exception expected, none thrown: passes.
* }
* <pre> public class SimpleExpectedExceptionTest {
* &#064;Rule
* public ExpectedException thrown= ExpectedException.none();
*
* &#064;Test
* public void throwsNullPointerException() {
* thrown.expect(NullPointerException.class);
* throw new NullPointerException();
* &#064;Test
* public void throwsNothing() {
* // no exception expected, none thrown: passes.
* }
*
* &#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;);
* &#064;Test
* public void throwsExceptionWithSpecificType() {
* thrown.expect(NullPointerException.class);
* throw new NullPointerException();
* }
* }</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.
*
* &#064;Test
* public void throwsIllegalArgumentExceptionWithMessageAndCause() {
* NullPointerException expectedCause = new NullPointerException();
* thrown.expect(IllegalArgumentException.class);
* thrown.expectMessage(&quot;What&quot;);
* thrown.expectCause(is(expectedCause));
* throw new IllegalArgumentException(&quot;What happened?&quot;, cause);
* }
* }
* </pre>
* <p>
* Instead of specifying the exception's type you can characterize the
* expected exception based on other criterias, too:
*
* By default ExpectedException rule doesn't handle AssertionErrors and
* AssumptionViolatedExceptions, because such exceptions are used by JUnit. If
* you want to handle such exceptions you have to call @link
* {@link #handleAssertionErrors()} or @link
* {@link #handleAssumptionViolatedExceptions()}.
* <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>
*
* <pre>
* // These tests all pass.
* public static class HasExpectedException {
* &#064;Rule
* public ExpectedException thrown= ExpectedException.none();
* <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>
*
* &#064;Test
* public void throwExpectedAssertionError() {
* thrown.handleAssertionErrors();
* thrown.expect(AssertionError.class);
* throw new AssertionError();
* }
* <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.
*
* &#064;Test
* public void throwExpectAssumptionViolatedException() {
* thrown.handleAssumptionViolatedExceptions();
* thrown.expect(AssumptionViolatedException.class);
* throw new AssumptionViolatedException(&quot;&quot;);
* }
* }
* </pre>
* <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 foo.". You can configure a different
* message by means of {@link #reportMissingExceptionWithMessage(String)}.
*
* @since 4.7
*/
public class ExpectedException implements TestRule {
/**
* @return a Rule that expects no exception to be thrown (identical to
* behavior without this Rule)
* 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 fMatcherBuilder = new ExpectedExceptionMatcherBuilder();

private boolean handleAssumptionViolatedExceptions = false;

private boolean handleAssertionErrors = false;

private String missingExceptionMessage;

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() {
handleAssertionErrors = true;
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() {
handleAssumptionViolatedExceptions = true;
return this;
}

/**
* Specifies the failure message for tests that are expected to throw
* an exception but do not throw any.
* @param message exception detail message
* @return self
* @return the rule itself
*/
public ExpectedException reportMissingExceptionWithMessage(String message) {
missingExceptionMessage = message;
Expand All @@ -129,40 +151,67 @@ public Statement apply(Statement base,
}

/**
* Adds {@code matcher} to the list of requirements for any thrown
* exception.
* 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) {
fMatcherBuilder.add(matcher);
}

/**
* Adds to the list of requirements for any thrown exception that it should
* be an instance of {@code type}
* 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();
* }
*/
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}
* 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));
}

/**
* Adds {@code matcher} to the list of requirements for the message returned
* from any thrown exception.
* 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));
}

/**
* Adds {@code matcher} to the list of requirements for the cause of
* any thrown exception.
* 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));
Expand All @@ -179,39 +228,28 @@ public ExpectedExceptionStatement(Statement base) {
public void evaluate() throws Throwable {
try {
fNext.evaluate();
} catch (AssumptionViolatedException e) {
optionallyHandleException(e, handleAssumptionViolatedExceptions);
return;
} catch (AssertionError e) {
optionallyHandleException(e, handleAssertionErrors);
return;
} catch (Throwable e) {
handleException(e);
return;
}
if (fMatcherBuilder.expectsThrowable()) {
if (isAnyExceptionExpected()) {
failDueToMissingException();
}
}
}

private void optionallyHandleException(Throwable e, boolean handleException)
throws Throwable {
if (handleException) {
handleException(e);
} else {
throw e;
}
}

private void handleException(Throwable e) throws Throwable {
if (fMatcherBuilder.expectsThrowable()) {
if (isAnyExceptionExpected()) {
assertThat(e, fMatcherBuilder.build());
} else {
throw e;
}
}

private boolean isAnyExceptionExpected() {
return fMatcherBuilder.expectsThrowable();
}

private void failDueToMissingException() throws AssertionError {
fail(missingExceptionMessage());
}
Expand Down
Loading

0 comments on commit 0f623cd

Please sign in to comment.