-
Notifications
You must be signed in to change notification settings - Fork 379
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
Implement matchers for thrown exceptions in Runnable
#423
Implement matchers for thrown exceptions in Runnable
#423
Conversation
Thanks for picking this up @ggalmazor, I really appreciate you helping out like this (and also to you @peterdemaeyer, apologies for not looking at your previous PR earlier). At a high level, I really like what this code is doing. However, I think we could make some usabilty improvements. My biggest gripe is that the code as stands requires an actual exception to match against. I prefer the JUnit approach of matching against the exception types (see https://junit.org/junit5/docs/current/user-guide/#writing-tests-exceptions-expected) Some example code that I think would work well in the context: import org.junit.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.exception.ThrowsException.throwsException;
public class ThrowsExceptionMatcherTest {
@Test
public void testThrows() {
assertThat(this::codeThatThrows, throwsException());
assertThat(this::codeThatThrows, throwsException(RuntimeException.class));
assertThat(this::codeThatThrows, throwsExceptionWithMessage("boom"));
assertThat(this::codeThatThrows, throwsExceptionWithMessage(containsString("oom")));
assertThat(this::codeThatThrows, throwsExceptionWithMessage(RuntimeException.class, "boom"));
assertThat(this::codeThatThrows, throwsExceptionWithMessage(RuntimeException.class, containsString("oom")));
}
public void codeThatThrows() {
throw new RuntimeException("boom");
}
} Some other odds and ends to think about:
|
Ah, thanks so much for your guidance @tumbarumba. I'll work on your suggestions asap 👍 |
The matcher matches specific exceptions for now (matching the extact class and message)
We can't assume the specific exception class will be accessible by test writers. They might need to use an exception superclass, which means the matcher must consider any exception subtype.
This matcher doesn't care about the exception's class
`ThrowsException` is now responsible for safely executing the runnable, extracting the exception (if any), and delegating the actual test to the composed matcher.
…ions. At heart, the matcher keeps checking instances, but some QoL alias support matching against types.
94e0c1e
to
0f12a81
Compare
@tumbarumba I finally got around to adding your suggestions to the PR. I've added QoL matcher factories to support different variations, providing types instead of instances, as you suggested. I agree with you that this was missing, and it adds much-needed versatility 👍 However, I have kept the matchers that target actual exceptions because I do think they bring value to enable Hamcrest users to build matchers that target exception internal state beyond their message, e.g., checking secondary causes or matching some public property. In my experience, this flexibility can be pivotal when dealing with third-party libraries that "get in your way" regarding error management. This is an "extended" version of the examples you used to describe how you'd like to see the matcher work (I hope you like it 😄): @Test
public void examples() {
RuntimeException boom = new RuntimeException("boom");
Runnable codeThatThrows = () -> {
throw boom;
};
assertThat(codeThatThrows, throwsException());
assertThat(codeThatThrows, throwsException(boom));
assertThat(codeThatThrows, throwsException(RuntimeException.class));
assertThat(codeThatThrows, throwsException("boom"));
assertThat(codeThatThrows, throwsException(withMessage("boom")));
assertThat(codeThatThrows, throwsException(withMessage(containsString("oom"))));
assertThat(codeThatThrows, throwsException(RuntimeException.class, "boom"));
assertThat(codeThatThrows, throwsException(RuntimeException.class, withMessage("boom")));
assertThat(codeThatThrows, throwsException(RuntimeException.class, withMessage(containsString("oom"))));
} I kept the I couldn't add variations for the message matcher due to type erasure conflicts with the exception matcher variants. The exception matcher would be more versatile between the two, which was another argument for me to keep it around. Besides that, I've also upgraded the tests to junit5 and added the aliases you requested. |
0fc1b95
to
9996b88
Compare
9996b88
to
a2d3269
Compare
Thanks @ggalmazor. I should get time to have a proper look this weekend. |
Hi @ggalmazor, I spent some time last week reviewing your code, and testing it out in various scenarios. Upon reflection, I think there are a number of changes I'd still like to see. Here are some of the things I was thinking about... The current implementation is spread across 3 classes (
In the end, I tried to see if I could merge the implementations. Starting with your code, I did some refactoring and ended up with this merged |
Thanks for taking the time to look into this, @tumbarumba. Your branch made it super easy for me to introduce the changes. I just had to tweak a few things to adapt the tests 👍 I also like that you migrated the class to extend from the self-diagnosing matcher superclass, which feels more robust and modular. A few notes:
Let me know what you think :) |
Looks good, @ggalmazor. Nice work! Can you please make one more minor change: you've reformatted all the javadoc in |
Yes, apologies! That was an unintended change |
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.
Looking good
Fixes #329
This PR is inspired by @peterdemaeyer's #330, which fell through the cracks two years ago. I want to rekindle this effort to have matchers for exceptions thrown in
Runnable
.The goal of this matcher is to provide an easy way to verify that some code will throw some expected exception by wrapping it inside a Runnable that's executed safely by the matcher and tests the thrown exception with the provided matchers, as in:
This is my first contribution to this repo, so I expect my changes won't align with current conventions. I've tried to imitate patterns I've seen in other
TypeSafeMatcher
subclasses, but I can't be sure if the result is 100% acceptable. Please point me in the right direction, and I'll apply any changes needed to merge this PR.In this PR:
throwsException
type-safe matcher forRunnable
throwsExceptionEqualTo
type-safe matcher forThrowable
throwsExceptionWithMessage
type-safe matcher forThrowable
Examples