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

Improved information with failed ExpectedException assertion #369

Merged
merged 11 commits into from May 24, 2012

Conversation

arjenw
Copy link
Contributor

@arjenw arjenw commented Jan 3, 2012

When using the ExpectedException to test for an exception to occur, it
throws an AssertionError when thrown exception does not match the
expected exception. Issue is however that in the JUnit runner results only
the actual exceptions message is shown and not the stacktrace. To fix
that, the actual exception is added to the cause of the AssertionError,
giving the exceptions in a format like:
java.lang.AssertionError: [reason]
Expected: [expected exception]
got: [actual exception + message]
at [AssertionError stacktrace]
Caused by: [actual exception + message]
at [actual exception stacktrace]

When using the ExpectedException to test for an exception to occur, it
throws an AssertionError when thrown exception does not match the
expected exception. Issue is however that in the JUnit runner results only
the actual exceptions message is shown and not the stacktrace. To fix
that, the actual exception is added to the cause of the AssertionError,
giving the exceptions in a format like:
java.lang.AssertionError: [reason]
  Expected: [expected exception]
     got: [actual exception + message]
 at [AssertionError stacktrace]
Caused by: [actual exception + message]
 at [actual exception stacktrace]
@KentBeck
Copy link
Member

KentBeck commented Jan 3, 2012

lgtm. David?

unknown added 2 commits January 3, 2012 17:36
When using the ExpectedException to test for an exception to occur, it
throws an AssertionError when thrown exception does not match the
expected exception. Issue is however that in the JUnit runner results only
the actual exceptions message is shown and not the stacktrace. To fix
that, the actual exception is added to the cause of the AssertionError,
giving the exceptions in a format like:
java.lang.AssertionError: [reason]
  Expected: [expected exception]
     got: [actual exception + message]
 at [AssertionError stacktrace]
Caused by: [actual exception + message]
 at [actual exception stacktrace]
@arjenw
Copy link
Contributor Author

arjenw commented Jan 3, 2012

I added a test case in the above commit

(they were the same) and relocated the changes to a better location
in the file which is more inline with the rest of the file. And,
of course, removed the superfluous brackets.
@arjenw
Copy link
Contributor Author

arjenw commented Jan 4, 2012

I am a bit doubting what the right place is for this fix. Right now I made the fix in ExpectedException. This nicely limits the scope of this fix to the usage of ExpectedException only. However, I also encountered a similar situation when using the Assert.assertThat() API with the actual parameter being an exception. That situation was without the ExpectedException usage, but was verifying whether an exception was thrown. So, the alternative of this fix is:

    public static <T> void assertThat(String reason, T actual,
            Matcher<? super T> matcher) {
        if (!matcher.matches(actual)) {
            Description description= new StringDescription();
            description.appendText(reason);
            description.appendText("\nExpected: ");
            description.appendDescriptionOf(matcher);
            description.appendText("\n     got: ");
            description.appendValue(actual);
            description.appendText("\n");
            java.lang.AssertionError assertionError= new java.lang.AssertionError(description.toString());
            if(actual instanceof Throwable) 
                assertionError.initCause((Throwable) actual);
            throw assertionError;
        }
    }

In which I replaced the line

throw new java.lang.AssertionError(description.toString());

with:

            java.lang.AssertionError assertionError= new java.lang.AssertionError(description.toString());
            if(actual instanceof Throwable) 
                assertionError.initCause((Throwable) actual);
            throw assertionError;

What is/are your opinion(s) on this? Where should this be fixed?

@Tibor17
Copy link
Contributor

Tibor17 commented Jan 4, 2012

@KentBeck

IMHO; in order to make a clear solution, one should not be able to use
java.lang.AssertionError in ExpectedException.

Reasons:

  1. The exception should not escape the "org.junit.*" packge;
  2. When a test fails, the writer of tests should not be able to operate with failure exception, i.e. java.lang.AssertionError - JUnit should have own one;
  3. The programmer should instead use
    @test(failed=true) and an use of other descriptive methods on @test should not be possible;
  4. java.lang.AssertionError is in conflicts with the JVM switch -ea (enabled assertions)
    .

Me as a JUnit features designer do not like the idea, that have to be dependent on AssertionError - it's very dirty solution.

@arjenw
Copy link
Contributor Author

arjenw commented Jan 4, 2012

@Tibor17
I am not sure whether I understand your comment correctly, but let me explain the reason I want to have this. Right now, when I make a test using the ExpectedException rule (or by using Assert.assertThat() directly), defining that I expect exception X to happen, JUnit will report me when instead of X, exception Y happened. However, it will report it as: java.lang.AssertionError: Expected X, but got Y (with the stacktrace of the assert statement only.

Now in order for me to find out why and where Y happened I have to debug the test first because I don't have the information of exception Y.

This solution proposal fixes that by adding Y as the cause of the AssertionError. This gives more insights in the error message and enables users to find the root cause faster and more easily.

that's a better place and has cleaner code than the original. Included
tests for both scenario's (assertThat as well as the ExpectedException).
@dsaff
Copy link
Member

dsaff commented Feb 1, 2012

@arjenw, I think that the version before this latest commit (where the logic was in ExpectedException) was the better take on this. Can we revert to that?

@arjenw
Copy link
Contributor Author

arjenw commented Feb 2, 2012

The main issue with the version before latest commit is that it only works for ExpectedException rules. This severely limits the use as all situations in which you want to assert on the Exception itself (for example with exception message / message code matchers) and validate some extra state after such an exception has been thrown, it's not usable. In such cases you want to be able to assert the expected exception has been thrown with Assert.assertThat().

However, when the wrong exception has been thrown you'll loose the exception information.

On the other hand I understand that having such a type specific action in the generic Assert.assertThat() is not desired. However there is no possibility to hook into that and write the right describer for a specific type.

So, the question remains: what should we do?

@@ -111,6 +111,7 @@ public void evaluate() throws Throwable {
} catch (Throwable e) {
if (fMatcher == null)
throw e;

Copy link
Member

Choose a reason for hiding this comment

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

Take out extra line here.

@dsaff
Copy link
Member

dsaff commented Feb 2, 2012

OK, I can see it your way. Please see inline comments.

* Moved CauseMatcher to ResultMatchers and splitted it up in two
matchers (failureIs and causedBy)
* Added tests for both matchers
@arjenw
Copy link
Contributor Author

arjenw commented Feb 29, 2012

What do you think about my latest changes (committed one month ago), in which I implemented the comments of @dsaff?

@Override
public boolean matchesSafely(Result item) {
for(Failure f: item.getFailures())
return failureMatcher.matches(f.getException());
Copy link
Member

Choose a reason for hiding this comment

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

We should beef up the javadoc. This "Matches if the first exception in the failure matches the given {@code exceptionMatcher}"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

* Fixed the javadoc of ResultMatchers.failureIs()
* Made the ResultMatchers.failureIs() matcher based on PrintableResult instead of Result
* Introduced method 'getFailures()' in PrintableResult to achieve that
* Moved causedBy() matcher from ResultMatchers to JUnitMatchers (as it is a Throwable matcher)
* Introduced the required tests at the right places
@arjenw
Copy link
Contributor Author

arjenw commented Apr 25, 2012

I reworked the code based on the suggestions. Please let me know if this is fine.

@dsaff
Copy link
Member

dsaff commented Apr 25, 2012

Thanks! I'm too swamped to review today, but will do so soon.

if (actual instanceof Throwable)
assertionError.initCause((Throwable) actual);
throw assertionError;
}
Copy link
Member

Choose a reason for hiding this comment

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

This brace looks oddly indented.

@dsaff
Copy link
Member

dsaff commented May 24, 2012

Sorry for the long-delayed review!

@arjenw
Copy link
Contributor Author

arjenw commented May 24, 2012

I implemented the required changes in the last two commits (sorry, struggling with GIT as I am not used to it).

@dsaff
Copy link
Member

dsaff commented May 24, 2012

Hmm. Looks like we're going to need to rebase to HEAD. Do you know how to do that?

Conflicts:
	src/test/java/org/junit/tests/AllTests.java
	src/test/java/org/junit/tests/experimental/rules/ExpectedExceptionRuleTest.java

Merged changes from the HEAD branch

Besides the merged changes also the approach to test for the failure has
been changed to comply to the new way of testing in
ExpectedExceptionTest. That no longer required the changes in
ResultMatchers (with their accompanying tests)
@arjenw
Copy link
Contributor Author

arjenw commented May 24, 2012

You meant this? I have not only pulled all changes, but also refactored my changes a bit such that it is using the new format of the ExpectedExceptionTest. Therefore I moved the 'failureIs' matcher from ResultMatchers to EventCollector (which allowed me to undo my changes in ResultMatchers, ResultMatchersTests and PrintableResult. Let me know what you think of it.

dsaff added a commit that referenced this pull request May 24, 2012
Improved information with failed ExpectedException assertion
@dsaff dsaff merged commit 99c4e59 into junit-team:master May 24, 2012
@dsaff
Copy link
Member

dsaff commented May 24, 2012

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants