How to use Hamcrest in Java to test for a exception?
Asked Answered
B

6

28

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.

Boost answered 31/12, 2014 at 18:1 Comment(2)
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)Sanctify
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);Bedmate
B
33

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.

Braille answered 31/12, 2014 at 18:32 Comment(9)
Awesome @mystarrocks, thank you. How is the @Rule+ExceptedException approach better than the @Test(expected=...) approach?Boost
@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.Braille
Updated the answer with explanation as to why this approach is better.Braille
Another advantage is that you can make an assertion on the exception message: exceptionGrabber.expectMessage("My expected message");Primus
It's gone unnoticed, or maybe I'm misreading something, but don't you want a semicolon after that doSomething() call?Bumper
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 immediatelyViolante
ExpectedException.none() is now deprecated and org.junit.Assert.assertThrows is now recommended. Assert.assertThrows(MyObjectifyNoSuchFieldException.class,() -> MyObjectifyUtil.getField(DownloadTask.class, fieldName))Nepali
ExpectedException.none() is deprecated answer should probably be updatedCashandcarry
Deprecated Since 4.13 Assert.assertThrows can be used to verify that your code throws a specific exception.Vicarious
S
13

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.

Shebeen answered 29/6, 2016 at 23:8 Comment(3)
if you change the interfaces to ...extends Exception, you can throw an Error like this: full code below: https://mcmap.net/q/487623/-how-to-use-hamcrest-in-java-to-test-for-a-exceptionChavis
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. :-(Pfister
The junit-utils library has implemented capability similar to this. See the related answer here: https://mcmap.net/q/487623/-how-to-use-hamcrest-in-java-to-test-for-a-exceptionPerineuritis
C
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.

Coltun answered 8/4, 2019 at 11:16 Comment(0)
M
11

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.
Monopetalous answered 17/6, 2021 at 22:18 Comment(0)
K
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

Kameko answered 28/8, 2020 at 19:16 Comment(0)
C
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 
    ...
Chavis answered 1/11, 2017 at 12:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.