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.
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.
AssumptionViolatedException;
import org.junit.runners.model.
Statement;
/**
* 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>
*
* <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 {
/**
* 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);
}
}