如何在Java中使用Hamcrest来测试异常?

时间:2014-12-31 18:01:40

标签: java junit hamcrest

如何使用Hamcrest测试异常?根据{{​​3}}中的评论,“Junit 4使用期望属性提供了异常处理。”

所以我尝试了这个,发现它有效:

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));      
    }

}

Hamcrest是否提供了超出JUnit @Test(expected=...)注释的任何其他功能?

当有人在Groovy(https://code.google.com/p/hamcrest/wiki/Tutorial)中询问此问题时,我的问题是用Java编写的单元测试。

6 个答案:

答案 0 :(得分:22)

您真的需要使用Hamcrest库吗?

如果没有,以下是Junit支持异常测试的方法。除了检查抛出的Exception类型之外,ExpectedException类还有许多方法可用于执行您想要的操作。

您可以将Hamcrest匹配器与此结合使用来断言特定内容,但最好让Junit期望抛出异常。

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);

        ...
    }

}

这种方法比

更好
  • @Test (expected=...)只接受@Test (expected=...) 测试方法执行是否通过抛出给定的异常而停止, 如果你要抛出异常的电话扔了一个。例如,即使doSomething方法抛出可能不合适的MyObjectifyNoSuchFieldException异常,测试也会成功

  • 您可以测试的不仅仅是抛出的异常类型。例如,您可以检查特定的异常实例或异常消息,依此类推

  • try/catch阻止方法,因为可读性和简洁性。

答案 1 :(得分:6)

如果计算断言错误描述(可能这就是为什么Hamcrest不提供这样的功能),我无法以一种很好的方式实现它,但如果你在Java 8上玩得很好,那么你可能会想要这样的东西(但是我不认为它会被使用,因为下面描述的问题):

IThrowingRunnable

此接口用于包装可能引发异常的代码。也可以使用Callable<E>,但后者需要返回一个值,所以我认为runnable(“void-callable”)更方便。

@FunctionalInterface
public interface IThrowingRunnable<E extends Throwable> {

    void run()
            throws E;

}

FailsWithMatcher

该类实现了一个匹配器,它需要给定的回调来抛出异常。这种实现的一个缺点是让回调抛出一个意外的异常(甚至不抛出一个异常)并没有描述什么是错误的,你会看到完全模糊的错误消息。

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

这是一个示例匹配器,用于简单检查抛出的异常消息。

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));
}

请注意这种方法:

  • ...是独立于测试运行者的
  • ...允许在单个测试中指定多个断言

最糟糕的是,典型的失败看起来像

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>

也许使用assertThat()方法的自定义实现可以修复它。

答案 2 :(得分:2)

我想最干净的方法是定义一个像这样的函数

public static Throwable exceptionOf(Callable<?> callable) {
    try {
        callable.call();
        return null;
    } catch (Throwable t) {
        return t;
    }
}

某个地方,然后例如呼叫

    assertThat(exceptionOf(() -> callSomethingThatShouldThrow()),
        instanceOf(TheExpectedException.class));

也许还使用类似this answer的ExceptionMessageMatcher之类的东西。

答案 3 :(得分:1)

从 junit 4.13 开始,您可以使用它的 Assert.assertThrows,如下所示:

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

在我看来,这种异常断言优于 @Test(expected=MyObjectifyNoSuchFieldException.class),因为您可以:

  • 进一步断言异常本身;
  • 断言关于副作用的事情(例如在你的模拟中);
  • 继续您的测试用例。

答案 4 :(得分:0)

除上述内容外。

如果将接口更改为... extends Exception,则可以抛出这样的错误:

   @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);
        }
    }

跟踪将如下所示:

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

答案 5 :(得分:0)

您应该使用junit-utils,它包含一个可以与Hamcrest的assertThat()方法一起使用的ExceptionMatcher。

示例1:

assertThat(() -> MyObjectifyNoSuchFieldException.class,
        throwsException(MyObjectifyNoSuchFieldException.class));

示例2:

assertThat(() -> myObject.doStuff(null),
        throwsException(MyObjectifyNoSuchFieldException.class)
            .withMessageContaining("ERR-120008"));

此处的其他详细信息:obvj.net/junit-utils