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
Conversation
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]
lgtm. David? |
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]
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.
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
with:
What is/are your opinion(s) on this? Where should this be fixed? |
IMHO; in order to make a clear solution, one should not be able to use Reasons:
Me as a JUnit features designer do not like the idea, that have to be dependent on AssertionError - it's very dirty solution. |
@Tibor17 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).
@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? |
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; | |||
|
There was a problem hiding this comment.
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.
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
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()); |
There was a problem hiding this comment.
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}"
There was a problem hiding this comment.
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
I reworked the code based on the suggestions. Please let me know if this is fine. |
Thanks! I'm too swamped to review today, but will do so soon. |
if (actual instanceof Throwable) | ||
assertionError.initCause((Throwable) actual); | ||
throw assertionError; | ||
} |
There was a problem hiding this comment.
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.
Sorry for the long-delayed review! |
…) and fixed the oddly indented brace
I implemented the required changes in the last two commits (sorry, struggling with GIT as I am not used to it). |
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)
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. |
Improved information with failed ExpectedException assertion
Thanks! |
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]