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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27724660/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 04:54:49  来源:igfitidea点击:

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

javajunithamcrest

提问by Michael Osofsky

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

如何使用 Hamcrest 测试异常?根据https://code.google.com/p/hamcrest/wiki/Tutorial 中的评论,“异常处理由 Junit 4 使用预期属性提供。”

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?

Hamcrest 是否提供除@Test(expected=...)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.

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

回答by mystarrocks

Should you really need to use the Hamcrestlibrary?

你真的需要使用Hamcrest图书馆吗?

If not, here's how you do it with Junit's support for exception testing. The ExpectedExceptionclass has a lot of methods that you can use to do what you want beyond checking the type of the thrown Exception.

如果没有,这里是如何使用Junit's 对异常测试的支持。该ExpectedException班有很多方法,您可以用来做你想要什么以后检查的抛出类型Exception

You can use the Hamcrestmatchers in combination with this to assert something specific, but it's better to let Junitexpect the thrown exceptions.

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

        ...
    }

}

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 exceptionthrew one. For example, the test will succeed even if doSomethingmethod threw the MyObjectifyNoSuchFieldExceptionexception 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/catchblock approach, because of readability and conciseness.

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

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

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

回答by Lyubomyr Shaydariv

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

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

IThrowingRunnable

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.

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

@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/127618319@6b143ee9>

Maybe using a custom implementation of the assertThat()method could fix it.

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

回答by Wolf

In addition to the above.

除了以上。

if you change the interfaces to ... extends Exception, you can throw an Error like this:

如果您将接口更改为 ... 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);
    }
}

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

回答by Hans-Peter St?rr

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.

也许也使用类似这个答案的 ExceptionMessageMatcher 的东西。