How do I use Hamcrest in Java to check for an exception?

How do I use Hamcrest to check for an exception? As per the comment at 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 works:

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 beyond @Test(expected=...)

JUnit?

While someone asked this in Groovy ( How do I use Hamcrest to check for exceptions? ), My question is about unit tests written in Java.

+9


source to share


3 answers


Do you really need to use the library Hamcrest

?

If not, how do you do it with support Junit

for testing exceptions. There are many methods in the class that you can use to do what you want besides checking the type of the cast . ExpectedException

Exception

You can use Hamcrest

Hamcrest in conjunction with this to assert something specific, but it's better to let Junit

expect exceptions to be thrown.



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 is better than

  • @Test (expected=...)

    because it @Test (expected=...)

    only checks if the execution of the method, the @Test (expected=...)

    given exception , is stopped , not if the call you wanted to call was throwing an exception. For example, the test will complete successfully, even if the method is an doSomething

    exception, MyObjectifyNoSuchFieldException

    which may not be desirable

  • You can check for more than just the type of the exception being thrown. For example, you can check for a specific instance of an exception or an exception message, etc.

  • Block approach try/catch

    due to readability and brevity.

+22


source


I wouldn't be able to implement this in a good way if I was counting assertion error descriptions (maybe that's why Hamcrest does not provide such an option), but if you play well with Java 8 then you might want something like this (however I don't think that it will ever be used due to the problems described below):

IThrowingRunnable

This interface is used to wrap code that could potentially throw exceptions. Can also be used Callable<E>

, but the latter requires a value to be returned, so I think runnable ("void-callable") is more convenient.

@FunctionalInterface
public interface IThrowingRunnable<E extends Throwable> {

    void run()
            throws E;

}

      

FailsWithMatcher

This class implements a mapping that requires this callback to throw an exception. The downside to this implementation is that having a callback that throws an unexpected exception (or even doesn't throw a single one) doesn't describe what is wrong and you will see completely 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 match example 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:

  • ... independent of the test participant
  • ... allows you to specify multiple assertions in one test

And worst of all, a typical failure 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>

      

Perhaps with a custom implementation of the method assertThat()

can fix it.

+5


source


In addition to the above.

if you change interfaces to ... extends Exception you can make an error like this:

   @Override
    protected boolean matchesSafely(final IThrowingRunnable<EX> runnable) {
        try {
            runnable.run();
            throw new Error("Did not throw Exception");
        } catch (final Exception ex) {
            return matcher.matches(ex);
        }
    }

      

Tracing

will look like this:

java.lang.Error: Did not throw Exception
    at de.test.test.FailsWithMatcher.matchesSafely(FailsWithMatcher.java:31)
    at de.test.test.FailsWithMatcher.matchesSafely(FailsWithMatcher.java:1)
    at org.hamcrest.TypeSafeMatcher.matches(TypeSafeMatcher.java:65)
    at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:12)
    at org.junit.Assert.assertThat(Assert.java:956)
    at org.junit.Assert.assertThat(Assert.java:923)
    at 
    ...

      

0


source







All Articles