26

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.

Community
  • 1
  • 1
Michael Osofsky
  • 11,429
  • 16
  • 68
  • 113
  • 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) – Andy Apr 28 '17 at 19:27
  • You can have a look at the assertj library which has an elegant api for asserting against exceptions: http://joel-costigliola.github.io/assertj/assertj-core-features-highlight.html#exception-assertion Example: assertThatThrownBy(() -> { throw new Exception("boom!"); }).isInstanceOf(Exception.class); – Sinisha Mihajlovski Apr 01 '18 at 11:11

6 Answers6

32

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.

mystarrocks
  • 4,040
  • 2
  • 36
  • 61
  • 2
    Awesome @mystarrocks, thank you. How is the @Rule+ExceptedException approach better than the @Test(expected=...) approach? – Michael Osofsky Dec 31 '14 at 20:04
  • 1
    @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 Dec 31 '14 at 21:08
  • Updated the answer with explanation as to why this approach is better. – mystarrocks Dec 31 '14 at 21:20
  • 2
    Another advantage is that you can make an assertion on the exception message: `exceptionGrabber.expectMessage("My expected message");` – eee Jan 01 '15 at 11:23
  • It's gone unnoticed, or maybe I'm misreading something, but don't you want a semicolon after that `doSomething()` call? – luskwater Apr 11 '18 at 16:33
  • Awesome, `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 – usr-local-ΕΨΗΕΛΩΝ Jun 12 '19 at 16:30
  • `ExpectedException.none()` is now deprecated and `org.junit.Assert.assertThrows` is now recommended. `Assert.assertThrows(MyObjectifyNoSuchFieldException.class,() -> MyObjectifyUtil.getField(DownloadTask.class, fieldName))` – Gautham M Sep 16 '20 at 09:22
  • ExpectedException.none() is deprecated answer should probably be updated – Jackie Apr 05 '21 at 19:51
12

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.

Oliver Gondža
  • 3,386
  • 4
  • 29
  • 49
Lyubomyr Shaydariv
  • 20,327
  • 12
  • 64
  • 105
  • if you change the interfaces to ...extends Exception, you can throw an Error like this: full code below: https://stackoverflow.com/a/47054422/834010 – Wolf Nov 01 '17 at 12:01
  • 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. :-( – Dr. Hans-Peter Störr Apr 08 '19 at 10:36
11

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.

djm.im
  • 3,295
  • 4
  • 30
  • 45
Dr. Hans-Peter Störr
  • 25,298
  • 30
  • 102
  • 139
8

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.
rslemos
  • 2,454
  • 22
  • 32
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

Oswaldo Junior
  • 116
  • 1
  • 5
0

In addition to the above.

if you change the interfaces to ... extends Exception, you can throw 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);
    }
}

trace 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 
    ...
djm.im
  • 3,295
  • 4
  • 30
  • 45
Wolf
  • 991
  • 1
  • 9
  • 12