Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes gh-121 (ExpectedException handles JUnit exceptions) #323

Merged
merged 5 commits into from Apr 9, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
102 changes: 82 additions & 20 deletions src/main/java/org/junit/rules/ExpectedException.java
@@ -1,17 +1,18 @@
package org.junit.rules;

import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertThat;
import static org.junit.matchers.JUnitMatchers.both;
import static org.junit.matchers.JUnitMatchers.containsString;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.StringDescription;
import org.junit.Assert;
import org.junit.internal.AssumptionViolatedException;
import org.junit.internal.matchers.TypeSafeMatcher;
import org.junit.runners.model.Statement;

/**
* The ExpectedException Rule allows in-test specification of expected exception
* The ExpectedException rule allows in-test specification of expected exception
* types and messages:
*
* <pre>
Expand All @@ -22,7 +23,7 @@
*
* &#064;Test
* public void throwsNothing() {
* // no exception expected, none thrown: passes.
* // no exception expected, none thrown: passes.
* }
*
* &#064;Test
Expand All @@ -40,31 +41,74 @@
* }
* }
* </pre>
*
* 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()}.
*
* <pre>
* // These tests all pass.
* public static class HasExpectedException {
* &#064;Rule
* public ExpectedException thrown= ExpectedException.none();
*
* &#064;Test
* public void throwExpectedAssertionError() {
* thrown.handleAssertionErrors();
* thrown.expect(AssertionError.class);
* throw new AssertionError();
* }
*
* &#064;Test
* public void throwExpectAssumptionViolatedException() {
* thrown.handleAssumptionViolatedExceptions();
* thrown.expect(AssumptionViolatedException.class);
* throw new AssumptionViolatedException(&quot;&quot;);
* }
* }
* </pre>
*/
public class ExpectedException implements TestRule {
/**
* @return a Rule that expects no exception to be thrown
* (identical to behavior without this Rule)
* @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 boolean handleAssumptionViolatedExceptions= false;

private boolean handleAssertionErrors= false;

private ExpectedException() {

}


public ExpectedException handleAssertionErrors() {
handleAssertionErrors= true;
return this;
}

public ExpectedException handleAssumptionViolatedExceptions() {
handleAssumptionViolatedExceptions= true;
return this;
}

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.
* 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.
// Should be able to remove this suppression in some brave new hamcrest
// world.
@SuppressWarnings("unchecked")
public void expect(Matcher<?> matcher) {
if (fMatcher == null)
Expand All @@ -74,24 +118,24 @@ public void expect(Matcher<?> matcher) {
}

/**
* Adds to the list of requirements for any thrown exception that it
* should be an instance of {@code type}
* 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}
* 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.
* 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));
Expand All @@ -108,10 +152,14 @@ 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) {
if (fMatcher == null)
throw e;
Assert.assertThat(e, fMatcher);
handleException(e);
return;
}
if (fMatcher != null)
Expand All @@ -120,17 +168,31 @@ public void evaluate() throws Throwable {
}
}

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

private void handleException(Throwable e) throws Throwable {
if (fMatcher == null)
throw e;
assertThat(e, 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());
}
};
}
}
}
4 changes: 2 additions & 2 deletions src/test/java/org/junit/tests/AllTests.java
Expand Up @@ -25,7 +25,7 @@
import org.junit.tests.experimental.parallel.ParallelMethodTest;
import org.junit.tests.experimental.rules.BlockJUnit4ClassRunnerOverrideTest;
import org.junit.tests.experimental.rules.ClassRulesTest;
import org.junit.tests.experimental.rules.ExpectedExceptionRuleTest;
import org.junit.tests.experimental.rules.ExpectedExceptionTest;
import org.junit.tests.experimental.rules.ExternalResourceRuleTest;
import org.junit.tests.experimental.rules.MethodRulesTest;
import org.junit.tests.experimental.rules.NameRulesTest;
Expand Down Expand Up @@ -144,7 +144,7 @@
ParentRunnerTest.class,
NameRulesTest.class,
ClassRulesTest.class,
ExpectedExceptionRuleTest.class,
ExpectedExceptionTest.class,
TempFolderRuleTest.class,
ExternalResourceRuleTest.class,
VerifierRuleTest.class,
Expand Down
159 changes: 159 additions & 0 deletions src/test/java/org/junit/tests/experimental/rules/EventCollector.java
@@ -0,0 +1,159 @@
package org.junit.tests.experimental.rules;

import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.matchers.JUnitMatchers.both;

import java.util.ArrayList;
import java.util.List;

import org.hamcrest.Matcher;
import org.junit.internal.matchers.TypeSafeMatcher;
import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;

class EventCollector extends RunListener {
static Matcher<EventCollector> everyTestRunSuccessful() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move static Matcher factories to the top of the class.

return both(hasNoFailure()).and(hasNoAssumptionFailure());
}

private static Matcher<EventCollector> hasNumberOfFailures(
final int numberOfFailures) {
return new TypeSafeMatcher<EventCollector>() {
@Override
public boolean matchesSafely(EventCollector item) {
return item.fFailures.size() == numberOfFailures;
}

public void describeTo(org.hamcrest.Description description) {
description.appendText("has ");
description.appendValue(numberOfFailures);
description.appendText(" failures");
}
};
}

static Matcher<EventCollector> hasSingleFailure() {
return hasNumberOfFailures(1);
}

static Matcher<EventCollector> hasNoFailure() {
return hasNumberOfFailures(0);
}

private static Matcher<EventCollector> hasNumberOfAssumptionFailures(
final int numberOfFailures) {
return new TypeSafeMatcher<EventCollector>() {
@Override
public boolean matchesSafely(EventCollector item) {
return item.fAssumptionFailures.size() == numberOfFailures;
}

public void describeTo(org.hamcrest.Description description) {
description.appendText("has ");
description.appendValue(numberOfFailures);
description.appendText(" assumption failures");
}
};
}

static Matcher<EventCollector> hasSingleAssumptionFailure() {
return hasNumberOfAssumptionFailures(1);
}

static Matcher<EventCollector> hasNoAssumptionFailure() {
return hasNumberOfAssumptionFailures(0);
}

static Matcher<EventCollector> hasSingleFailureWithMessage(String message) {
return hasSingleFailureWithMessage(equalTo(message));
}

static Matcher<EventCollector> hasSingleFailureWithMessage(
final Matcher<String> messageMatcher) {
return new TypeSafeMatcher<EventCollector>() {
@Override
public boolean matchesSafely(EventCollector item) {
return hasSingleFailure().matches(item)
&& messageMatcher.matches(item.fFailures.get(0)
.getMessage());
}

public void describeTo(org.hamcrest.Description description) {
description.appendText("has single failure with message ");
messageMatcher.describeTo(description);
}
};
}

private final List<Description> fTestRunsStarted= new ArrayList<Description>();

private final List<Result> fTestRunsFinished= new ArrayList<Result>();

private final List<Description> fTestsStarted= new ArrayList<Description>();

private final List<Description> fTestsFinished= new ArrayList<Description>();

private final List<Failure> fFailures= new ArrayList<Failure>();

private final List<Failure> fAssumptionFailures= new ArrayList<Failure>();

private final List<Description> fTestsIgnored= new ArrayList<Description>();

@Override
public void testRunStarted(Description description) throws Exception {
fTestRunsStarted.add(description);
}

@Override
public void testRunFinished(Result result) throws Exception {
fTestRunsFinished.add(result);
}

@Override
public void testStarted(Description description) throws Exception {
fTestsStarted.add(description);
}

@Override
public void testFinished(Description description) throws Exception {
fTestsFinished.add(description);
}

@Override
public void testFailure(Failure failure) throws Exception {
fFailures.add(failure);
}

@Override
public void testAssumptionFailure(Failure failure) {
fAssumptionFailures.add(failure);
}

@Override
public void testIgnored(Description description) throws Exception {
fTestsIgnored.add(description);
}

@Override
public String toString() {
StringBuilder sb= new StringBuilder();
sb.append(fTestRunsStarted.size());
sb.append(" test runs started, ");
sb.append(fTestRunsFinished.size());
sb.append(" test runs finished, ");
sb.append(fTestsStarted.size());
sb.append(" tests started, ");
sb.append(fTestsFinished.size());
sb.append(" tests finished, ");
sb.append(fFailures.size());
sb.append(" failures, ");
sb.append(fAssumptionFailures.size());
sb.append(" assumption failures, ");
sb.append(fTestsIgnored.size());
sb.append(" tests ignored");

return sb.toString();
}
}