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.
In order to test the exception thrown by any method in JUnit 4, you need to use @Test(expected=IllegalArgumentException. class) annotation. You can replace IllegalArgumentException. class with any other exception e.g. NullPointerException.
To test the exceptions, we should follow the following steps: Create a class to be tested. Create a test case class for testing exceptions. Create a Test Runner class to execute the test case.
Hamcrest is a framework that assists writing software tests in the Java programming language. It supports creating customized assertion matchers ('Hamcrest' is an anagram of 'matchers'), allowing match rules to be defined declaratively. These matchers have uses in unit testing frameworks such as JUnit and jMock.
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 if doSomething
method threw the MyObjectifyNoSuchFieldException
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.
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):
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;
}
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);
}
}
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);
}
}
@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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With