How to use Hamcrest in Java to test for a exception?
Solution 1
Do you really need to use the Hamcrest
library?
If not, here's how you do it with Junit
's support for exception testing. The ExpectedException
class has a lot of methods that you can use to do what you want beyond checking the type of the thrown Exception
.
You can use the Hamcrest
matchers in combination with this to assert something specific, but it's better to let Junit
expect the thrown exceptions.
public class MyObjectifyUtilTest {
// create a rule for an exception grabber that you can use across
// the methods in this test class
@Rule
public ExpectedException exceptionGrabber = ExpectedException.none();
@Test
public void shouldThrowExceptionBecauseFieldDoesNotExist() throws MyObjectifyNoSuchFieldException {
String fieldName = "someMissingField";
// a method capable of throwing MyObjectifyNoSuchFieldException too
doSomething();
// assuming the MyObjectifyUtil.getField would throw the exception,
// I'm expecting an exception to be thrown just before that method call
exceptionGrabber.expect(MyObjectifyNoSuchFieldException.class);
MyObjectifyUtil.getField(DownloadTask.class, fieldName);
...
}
}
This approach better than the
-
@Test (expected=...)
approach because@Test (expected=...)
only tests if the method execution halts by throwing the given exception, not if the call you wanted to throw the exception threw one. For example, the test will succeed even ifdoSomething
method threw theMyObjectifyNoSuchFieldException
exception which may not be desirable -
You get to test more than just the type of the exception being thrown. For example, you could check for a particular exception instance or exception message and so on
-
The
try/catch
block approach, because of readability and conciseness.
Solution 2
I couldn't implement it in a nice way if counting assertion error descriptions (probably this is why Hamcrest does not provide such a feature), but if you're playing well with Java 8 then you might want something like this (however I don't think it would be ever used because of the issues described below):
IThrowingRunnable
This interface is used to wrap code that could potentially throw exceptions. Callable<E>
might be used as well, but the latter requires a value to be returned, so I think that a runnable ("void-callable") is more convenient.
@FunctionalInterface
public interface IThrowingRunnable<E extends Throwable> {
void run()
throws E;
}
FailsWithMatcher
This class implements a matcher that requires the given callback to throw an exception. A disadvantage of this implementation is that having a callback throwing an unexpected exception (or even not throwing a single) does not describe what's wrong and you'd see totally obscure error messages.
public final class FailsWithMatcher<EX extends Throwable>
extends TypeSafeMatcher<IThrowingRunnable<EX>> {
private final Matcher<? super EX> matcher;
private FailsWithMatcher(final Matcher<? super EX> matcher) {
this.matcher = matcher;
}
public static <EX extends Throwable> Matcher<IThrowingRunnable<EX>> failsWith(final Class<EX> throwableType) {
return new FailsWithMatcher<>(instanceOf(throwableType));
}
public static <EX extends Throwable> Matcher<IThrowingRunnable<EX>> failsWith(final Class<EX> throwableType, final Matcher<? super EX> throwableMatcher) {
return new FailsWithMatcher<>(allOf(instanceOf(throwableType), throwableMatcher));
}
@Override
protected boolean matchesSafely(final IThrowingRunnable<EX> runnable) {
try {
runnable.run();
return false;
} catch ( final Throwable ex ) {
return matcher.matches(ex);
}
}
@Override
public void describeTo(final Description description) {
description.appendText("fails with ").appendDescriptionOf(matcher);
}
}
ExceptionMessageMatcher
This is a sample matcher to make a simple check for the thrown exception message.
public final class ExceptionMessageMatcher<EX extends Throwable>
extends TypeSafeMatcher<EX> {
private final Matcher<? super String> matcher;
private ExceptionMessageMatcher(final Matcher<String> matcher) {
this.matcher = matcher;
}
public static <EX extends Throwable> Matcher<EX> exceptionMessage(final String message) {
return new ExceptionMessageMatcher<>(is(message));
}
@Override
protected boolean matchesSafely(final EX ex) {
return matcher.matches(ex.getMessage());
}
@Override
public void describeTo(final Description description) {
description.appendDescriptionOf(matcher);
}
}
And the test sample itself
@Test
public void test() {
assertThat(() -> emptyList().get(0), failsWith(IndexOutOfBoundsException.class, exceptionMessage("Index: 0")));
assertThat(() -> emptyList().set(0, null), failsWith(UnsupportedOperationException.class));
}
Note that this approach:
- ... is test-runner-independent
- ... allows to specify multiple assertions in a single test
And the worst thing, a typical fail will look like
java.lang.AssertionError:
Expected: fails with (an instance of java.lang.IndexOutOfBoundsException and is "Index: 0001")
but: was <foo.bar.baz.FailsWithMatcherTest$$Lambda$1/127618319@6b143ee9>
Maybe using a custom implementation of the assertThat()
method could fix it.
Solution 3
I suppose the cleanest way is to define a function like
public static Throwable exceptionOf(Callable<?> callable) {
try {
callable.call();
return null;
} catch (Throwable t) {
return t;
}
}
somewhere and then e.g. call
assertThat(exceptionOf(() -> callSomethingThatShouldThrow()),
instanceOf(TheExpectedException.class));
perhaps also using something like the ExceptionMessageMatcher of this answer.
Solution 4
Since junit 4.13 you can use its Assert.assertThrows
, like this:
import static org.junit.Assert.assertThrows;
...
MyObjectifyNoSuchFieldException ex = assertThrows(MyObjectifyNoSuchFieldException.class, () -> MyObjectifyUtil.getField(DownloadTask.class, fieldName));
// now you can go further and assert things about the exception ex
// if MyObjectifyUtil.getField(...) does not throw exception, the test will fail right at assertThrows
In my opinion this sort of exceptions asserting is superior to @Test(expected=MyObjectifyNoSuchFieldException.class)
because you can:
- further assert things about the exception itself;
- assert things about side effects (in your mocks, for example);
- continue your test case.
Solution 5
You should use junit-utils
, which does contain an ExceptionMatcher that can be used together with Hamcrest's assertThat()
method.
Example 1:
assertThat(() -> MyObjectifyNoSuchFieldException.class,
throwsException(MyObjectifyNoSuchFieldException.class));
Example 2:
assertThat(() -> myObject.doStuff(null),
throwsException(MyObjectifyNoSuchFieldException.class)
.withMessageContaining("ERR-120008"));
Additional details here: obvj.net/junit-utils
Related videos on Youtube
Michael Osofsky
Interested in Behavior Driven Development (BDD), Test Driven Development (TDD), Vipassana meditation, innovation, transgender issues, and chocolate.
Updated on December 22, 2021Comments
-
Michael Osofsky over 2 years
How do I use Hamcrest to test for an exception? According to a comment in https://code.google.com/p/hamcrest/wiki/Tutorial, "Exception handling is provided by Junit 4 using the expected attribute."
So I tried this and found that it worked:
public class MyObjectifyUtilTest { @Test public void shouldFindFieldByName() throws MyObjectifyNoSuchFieldException { String fieldName = "status"; String field = MyObjectifyUtil.getField(DownloadTask.class, fieldName); assertThat(field, equalTo(fieldName)); } @Test(expected=MyObjectifyNoSuchFieldException.class) public void shouldThrowExceptionBecauseFieldDoesNotExist() throws MyObjectifyNoSuchFieldException { String fieldName = "someMissingField"; String field = MyObjectifyUtil.getField(DownloadTask.class, fieldName); assertThat(field, equalTo(fieldName)); } }
Does Hamcrest provide any additional functionality above and beyond the
@Test(expected=...)
annotation from JUnit?While someone asked about this in Groovy (How to use Hamcrest to test for exception?), my question is for unit tests written in Java.
-
Andy almost 7 yearsHeh, of COURSE JUnit doesn't have a well-established way of testing that a specific operation throws...lack of functional programming for the fail (before Java 8, that is)
-
Sinisha Mihajlovski about 6 yearsYou can have a look at the assertj library which has an elegant api for asserting against exceptions: joel-costigliola.github.io/assertj/… Example: assertThatThrownBy(() -> { throw new Exception("boom!"); }).isInstanceOf(Exception.class);
-
-
Michael Osofsky over 9 yearsAwesome @mystarrocks, thank you. How is the @Rule+ExceptedException approach better than the @Test(expected=...) approach?
-
mystarrocks over 9 years@MichaelOsofsky, it's better in that you get to test the exact method call that you think could throw an exception threw one this time. Contrast that to the method level annotation which doesn't tell you which line threw a given exception. Say if I had a line before the line you suspect could throw a given exception threw the same exception, your test should have actually failed, but wouldn't with the annotation approach. What the
@Test
annotation approach really cares is the method execution halts throwing the given exception, not about which call really threw that exception. -
mystarrocks over 9 yearsUpdated the answer with explanation as to why this approach is better.
-
eee over 9 yearsAnother advantage is that you can make an assertion on the exception message:
exceptionGrabber.expectMessage("My expected message");
-
Wolf over 6 yearsif you change the interfaces to ...extends Exception, you can throw an Error like this: full code below: stackoverflow.com/a/47054422/834010
-
luskwater about 6 yearsIt's gone unnoticed, or maybe I'm misreading something, but don't you want a semicolon after that
doSomething()
call? -
Hans-Peter Störr about 5 yearsNice. If the "but: was ..." message would display the thrown exception I'd be very happy with this. But unfortunately I don't see a way to do this without making either storing state in the matcher, or calling the lambda twice, which would often create a different result. :-(
-
usr-local-ΕΨΗΕΛΩΝ almost 5 yearsAwesome,
exception
al, but... "Do you really need Hamcrest?" ==> The ExpectedException class supports Hamcrest matchers. I would have liked to downvote for this reason, but, really, the answer is too awesome to downvote! I'll start using this rule immediately -
Gautham M over 3 years
ExpectedException.none()
is now deprecated andorg.junit.Assert.assertThrows
is now recommended.Assert.assertThrows(MyObjectifyNoSuchFieldException.class,() -> MyObjectifyUtil.getField(DownloadTask.class, fieldName))
-
Jackie about 3 yearsExpectedException.none() is deprecated answer should probably be updated