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

Mic*_*sky 11 java junit hamcrest

如何使用Hamcrest测试异常?根据https://code.google.com/p/hamcrest/wiki/Tutorial中的评论,"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));      
    }

}
Run Code Online (Sandbox Code Playgroud)

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

虽然有人在Groovy中询问过这个问题(如何使用Hamcrest来测试异常?),我的问题是用Java编写的单元测试.

mys*_*cks 26

你真的需要使用这个Hamcrest库吗?

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

您可以将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);

        ...
    }

}
Run Code Online (Sandbox Code Playgroud)

这种方法比

  • @Test (expected=...)方法,因为@Test (expected=...)只测试方法执行是否通过抛出给定的异常而停止,而不是如果你想抛出异常的调用抛出一个.例如,即使doSomething方法抛出了MyObjectifyNoSuchFieldException可能不合需要的异常,测试也会成功

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

  • try/catch块方法,因为可读性和简洁.

  • 太棒了@mystarrocks,谢谢。@Rule+ExceptedException 方法比 @Test(expected=...) 方法如何更好? (2认同)
  • 另一个优点是您可以对异常消息进行断言:`exceptionGrabber.expectMessage("My Expected message");` (2认同)

rsl*_*mos 11

从 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
Run Code Online (Sandbox Code Playgroud)

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

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


Lyu*_*riv 6

如果计算断言错误描述,我无法以一种很好的方式实现它(这可能就是Hamcrest不提供这种功能的原因),但是如果您使用Java 8很好,那么您可能会想要这样的东西(但是我不这样做)认为由于以下问题,它永远不会使用):

IThrowingRunnable

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

@FunctionalInterface
public interface IThrowingRunnable<E extends Throwable> {

    void run()
            throws E;

}
Run Code Online (Sandbox Code Playgroud)

与匹配失败

此类实现匹配器,该匹配器要求给定的回调引发异常。此实现的缺点是,使回调引发意外的异常(甚至不引发单个异常)不会描述错误的原因,并且您会看到完全模糊的错误消息。

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

}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

还有测试样本本身

@Test
public void test() {
    assertThat(() -> emptyList().get(0), failsWith(IndexOutOfBoundsException.class, exceptionMessage("Index: 0")));
    assertThat(() -> emptyList().set(0, null), failsWith(UnsupportedOperationException.class));
}
Run Code Online (Sandbox Code Playgroud)

注意这种方法:

  • ...与测试运行者无关
  • ...允许在一个测试中指定多个断言

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

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>
Run Code Online (Sandbox Code Playgroud)

也许使用该assertThat()方法的自定义实现可以解决该问题。


Han*_*örr 5

我想最干净的方法是定义一个像

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

某个地方然后打电话

assertThat(exceptionOf(() -> callSomethingThatShouldThrow()),
    instanceOf(TheExpectedException.class));
Run Code Online (Sandbox Code Playgroud)

也许还使用类似于答案的ExceptionMessageMatcher之类的东西。


小智 5

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

示例1:

assertThat(() -> MyObjectifyNoSuchFieldException.class,
        throwsException(MyObjectifyNoSuchFieldException.class));
Run Code Online (Sandbox Code Playgroud)

示例2:

assertThat(() -> myObject.doStuff(null),
        throwsException(MyObjectifyNoSuchFieldException.class)
            .withMessageContaining("ERR-120008"));
Run Code Online (Sandbox Code Playgroud)

其他详细信息请参见:obvj.net/junit-utils