How to use Hamcrest in Java to test for a exception?

35,530

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 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.

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

Share:
35,530

Related videos on Youtube

Michael Osofsky
Author by

Michael Osofsky

Interested in Behavior Driven Development (BDD), Test Driven Development (TDD), Vipassana meditation, innovation, transgender issues, and chocolate.

Updated on December 22, 2021

Comments

  • Michael Osofsky
    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
      Andy almost 7 years
      Heh, 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
      Sinisha Mihajlovski about 6 years
      You 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
    Michael Osofsky over 9 years
    Awesome @mystarrocks, thank you. How is the @Rule+ExceptedException approach better than the @Test(expected=...) approach?
  • mystarrocks
    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
    mystarrocks over 9 years
    Updated the answer with explanation as to why this approach is better.
  • eee
    eee over 9 years
    Another advantage is that you can make an assertion on the exception message: exceptionGrabber.expectMessage("My expected message");
  • Wolf
    Wolf over 6 years
    if you change the interfaces to ...extends Exception, you can throw an Error like this: full code below: stackoverflow.com/a/47054422/834010
  • luskwater
    luskwater about 6 years
    It's gone unnoticed, or maybe I'm misreading something, but don't you want a semicolon after that doSomething() call?
  • Hans-Peter Störr
    Hans-Peter Störr about 5 years
    Nice. 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-ΕΨΗΕΛΩΝ
    usr-local-ΕΨΗΕΛΩΝ almost 5 years
    Awesome, exceptional, 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
    Gautham M over 3 years
    ExpectedException.none() is now deprecated and org.junit.Assert.assertThrows is now recommended. Assert.assertThrows(MyObjectifyNoSuchFieldException.class,() -> MyObjectifyUtil.getField(DownloadTask.class, fieldName))
  • Jackie
    Jackie about 3 years
    ExpectedException.none() is deprecated answer should probably be updated