在 Java 中,如何验证 JUnit 抛出的异常?

发布于 2024-07-18 02:59:53 字数 320 浏览 5 评论 0原文

为 Java API 编写单元测试时,有时您可能希望对异常执行更详细的验证。 即比 JUnit 提供的 @test 注释提供的更多。

例如,考虑一个类应该从其他接口捕获异常,包装该异常并抛出包装的异常。 您可能想要验证:

  1. 引发包装异常的确切方法调用。
  2. 包装器异常将原始异常作为其原因。
  3. 包装器异常的消息。

这里的要点是,您希望在单元测试中对异常进行额外的验证(而不是关于您是否应该验证异常消息之类的内容的争论)。

对此有什么好的方法吗?

When writing unit tests for a Java API there may be circumstances where you want to perform more detailed validation of an exception. I.e. more than is offered by the @test annotation offered by JUnit.

For example, consider an class that should catch an exception from some other Interface, wrap that exception and throw the wrapped exception. You may want to verify:

  1. The exact method call that throws the wrapped exception.
  2. That the wrapper exception has the original exception as its cause.
  3. The message of the wrapper exception.

The main point here is that you want to be perf additional validation of an exception in a unit test (not a debate about whether you should verify things like the exception message).

What's a good approach for this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

小草泠泠 2024-07-25 02:59:53

在 JUnit 4 中,可以使用 ExpectedException 规则轻松完成。

这是 javadocs 中的示例:

// These tests all pass.
public static class HasExpectedException {
    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Test
    public void throwsNothing() {
        // no exception expected, none thrown: passes.
    }

    @Test
    public void throwsNullPointerException() {
        thrown.expect(NullPointerException.class);
        throw new NullPointerException();
    }

    @Test
    public void throwsNullPointerExceptionWithMessage() {
        thrown.expect(NullPointerException.class);
        thrown.expectMessage("happened?");
        thrown.expectMessage(startsWith("What"));
        throw new NullPointerException("What happened?");
    }
}

In JUnit 4 it can be easily done using ExpectedException rule.

Here is example from javadocs:

// These tests all pass.
public static class HasExpectedException {
    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Test
    public void throwsNothing() {
        // no exception expected, none thrown: passes.
    }

    @Test
    public void throwsNullPointerException() {
        thrown.expect(NullPointerException.class);
        throw new NullPointerException();
    }

    @Test
    public void throwsNullPointerExceptionWithMessage() {
        thrown.expect(NullPointerException.class);
        thrown.expectMessage("happened?");
        thrown.expectMessage(startsWith("What"));
        throw new NullPointerException("What happened?");
    }
}
那些过往 2024-07-25 02:59:53

您的答案<中提供的/a>,这是一个好方法。 除此之外:

您可以将函数 expectException 包装到一个新的注释中,称为 ExpectedException
带注释的方法看起来像这样:

@Test
@ExpectedException(class=WrapperException.class, message="Exception Message", causeException)
public void testAnExceptionWrappingFunction() {
  //whatever you test
}

这种方式会更具可读性,但它是完全相同的方法。

另一个原因是:我喜欢注释:)

As provided in your answer, it's a good approach. In addition to this:

You could wrap the function expectException into a new Annotation, called ExpectedException.
An annotated method would look like this:

@Test
@ExpectedException(class=WrapperException.class, message="Exception Message", causeException)
public void testAnExceptionWrappingFunction() {
  //whatever you test
}

This way would be more readable, but it's exactly the same approach.

Another reason is: I like Annotations :)

北风几吹夏 2024-07-25 02:59:53

看看建议的答案,你可以真正感受到 Java 中没有闭包的痛苦。 恕我直言,最易读的解决方案是古老的 try catch。

@Test
public void test() {
    ...
    ...
    try {
        ...
        fail("No exception caught :(");
    }
    catch (RuntimeException ex) {
        assertEquals(Whatever.class, ex.getCause().getClass());
        assertEquals("Message", ex.getMessage());
    }
}

Looking at the proposed answers, you can really feel the pain of not having closures in Java. IMHO, the most readable solution is ye good old try catch.

@Test
public void test() {
    ...
    ...
    try {
        ...
        fail("No exception caught :(");
    }
    catch (RuntimeException ex) {
        assertEquals(Whatever.class, ex.getCause().getClass());
        assertEquals("Message", ex.getMessage());
    }
}
平安喜乐 2024-07-25 02:59:53

对于 JUNIT 3.x

public void test(){
   boolean thrown = false;
   try{
      mightThrowEx();
   } catch ( Surprise expected ){
      thrown = true;
      assertEquals( "message", expected.getMessage());
   }
  assertTrue(thrown );
}

For JUNIT 3.x

public void test(){
   boolean thrown = false;
   try{
      mightThrowEx();
   } catch ( Surprise expected ){
      thrown = true;
      assertEquals( "message", expected.getMessage());
   }
  assertTrue(thrown );
}
原来是傀儡 2024-07-25 02:59:53

在这篇文章之前,我已经通过这样做完成了异常验证:

try {
    myObject.doThings();
    fail("Should've thrown SomeException!");
} catch (SomeException e) {
    assertEquals("something", e.getSomething());
}

不过,我花了一些时间思考这个问题,并提出了以下建议(Java5,JUnit 3.x):

// Functor interface for exception assertion.
public interface AssertionContainer<T extends Throwable> {
    void invoke() throws T;
    void validate(T throwable);
    Class<T> getType();
}

// Actual assertion method.
public <T extends Throwable> void assertThrowsException(AssertionContainer<T> functor) {
    try {
        functor.invoke();
        fail("Should've thrown "+functor.getType()+"!");
    } catch (Throwable exc) {
        assertSame("Thrown exception was of the wrong type! Expected "+functor.getClass()+", actual "+exc.getType(),
                   exc.getClass(), functor.getType());
        functor.validate((T) exc);
    }
}

// Example implementation for servlet I used to actually test this. It was an inner class, actually.
AssertionContainer<ServletException> functor = new AssertionContainer<ServletException>() {
    public void invoke() throws ServletException {
        servlet.getRequiredParameter(request, "some_param");
    }

    public void validate(ServletException e) {
        assertEquals("Parameter \"some_param\" wasn't found!", e.getMessage());
    }

    public Class<ServletException> getType() {
        return ServletException.class;
    }
}

// And this is how it's used.
assertThrowsException(functor);

看着这两个,我无法决定哪一个我想要更多。 我想这是实现目标的问题之一(在我的例子中,带有仿函数参数的断言方法)从长远来看是不值得的,因为执行 6+ 代码来断言 try 会容易得多..catch 块。

话又说回来,也许我周五晚上 10 分钟解决问题的结果并不是最明智的方法。

Until this post I've done my exception validation by doing this:

try {
    myObject.doThings();
    fail("Should've thrown SomeException!");
} catch (SomeException e) {
    assertEquals("something", e.getSomething());
}

I spent a few moments thinking about the issue though and came up with the following (Java5, JUnit 3.x):

// Functor interface for exception assertion.
public interface AssertionContainer<T extends Throwable> {
    void invoke() throws T;
    void validate(T throwable);
    Class<T> getType();
}

// Actual assertion method.
public <T extends Throwable> void assertThrowsException(AssertionContainer<T> functor) {
    try {
        functor.invoke();
        fail("Should've thrown "+functor.getType()+"!");
    } catch (Throwable exc) {
        assertSame("Thrown exception was of the wrong type! Expected "+functor.getClass()+", actual "+exc.getType(),
                   exc.getClass(), functor.getType());
        functor.validate((T) exc);
    }
}

// Example implementation for servlet I used to actually test this. It was an inner class, actually.
AssertionContainer<ServletException> functor = new AssertionContainer<ServletException>() {
    public void invoke() throws ServletException {
        servlet.getRequiredParameter(request, "some_param");
    }

    public void validate(ServletException e) {
        assertEquals("Parameter \"some_param\" wasn't found!", e.getMessage());
    }

    public Class<ServletException> getType() {
        return ServletException.class;
    }
}

// And this is how it's used.
assertThrowsException(functor);

Looking at these two I can't decide which one I like more. I guess this is one of those issues where achieving a goal (in my case, the assertion method with functor parameter) isn't worth it in the long run since it's just a lot easier to do those 6+ of code to assert the try..catch block.

Then again, maybe my 10 minute result of problem solving at friday evening just isn't the most intelligent way to do this.

很酷不放纵 2024-07-25 02:59:53

@akuhn:

即使没有闭包,我们也可以获得更具可读性的解决方案(使用 catch-exception ):

import static com.googlecode.catchexception.CatchException.*;

public void test() {
    ...
    ...
    catchException(nastyBoy).doNastyStuff();
    assertTrue(caughtException() instanceof WhateverException);
    assertEquals("Message", caughtException().getMessage());
}

@akuhn:

Even without closures we can get a more readable solution (using catch-exception):

import static com.googlecode.catchexception.CatchException.*;

public void test() {
    ...
    ...
    catchException(nastyBoy).doNastyStuff();
    assertTrue(caughtException() instanceof WhateverException);
    assertEquals("Message", caughtException().getMessage());
}
十雾 2024-07-25 02:59:53

以下帮助程序方法(改编自这篇博客文章)技巧:

/**
 * Run a test body expecting an exception of the
 * given class and with the given message.
 *
 * @param test              To be executed and is expected to throw the exception.
 * @param expectedException The type of the expected exception.
 * @param expectedMessage   If not null, should be the message of the expected exception.
 * @param expectedCause     If not null, should be the same as the cause of the received exception.
 */
public static void expectException(
        Runnable test,
        Class<? extends Throwable> expectedException,
        String expectedMessage,
        Throwable expectedCause) {
    try {
        test.run();
    }
    catch (Exception ex) {
        assertSame(expectedException, ex.getClass());
        if (expectedMessage != null) {
            assertEquals(expectedMessage, ex.getMessage());
        }

        if (expectedCause != null) {
            assertSame(expectedCause, ex.getCause());
        }

        return;
    }

    fail("Didn't find expected exception of type " + expectedException.getName());
}

测试代码可以按如下方式调用它:

TestHelper.expectException(
        new Runnable() {
            public void run() {
                classInstanceBeingTested.methodThatThrows();
            }
        },
        WrapperException.class,
        "Exception Message",
        causeException
);

The following helper method (adapted from this blog post) does the trick:

/**
 * Run a test body expecting an exception of the
 * given class and with the given message.
 *
 * @param test              To be executed and is expected to throw the exception.
 * @param expectedException The type of the expected exception.
 * @param expectedMessage   If not null, should be the message of the expected exception.
 * @param expectedCause     If not null, should be the same as the cause of the received exception.
 */
public static void expectException(
        Runnable test,
        Class<? extends Throwable> expectedException,
        String expectedMessage,
        Throwable expectedCause) {
    try {
        test.run();
    }
    catch (Exception ex) {
        assertSame(expectedException, ex.getClass());
        if (expectedMessage != null) {
            assertEquals(expectedMessage, ex.getMessage());
        }

        if (expectedCause != null) {
            assertSame(expectedCause, ex.getCause());
        }

        return;
    }

    fail("Didn't find expected exception of type " + expectedException.getName());
}

The test code can then invoke this as follows:

TestHelper.expectException(
        new Runnable() {
            public void run() {
                classInstanceBeingTested.methodThatThrows();
            }
        },
        WrapperException.class,
        "Exception Message",
        causeException
);
万劫不复 2024-07-25 02:59:53

我做了一些非常简单的事情

testBla(){
    try {
        someFailingMethod()
        fail(); //method provided by junit
    } catch(Exception e) {
          //do nothing
    }
}

i did something very simple

testBla(){
    try {
        someFailingMethod()
        fail(); //method provided by junit
    } catch(Exception e) {
          //do nothing
    }
}
月亮邮递员 2024-07-25 02:59:53

对于 JUnit 5 来说要容易得多:

    @Test
    void testAppleIsSweetAndRed() throws Exception {

        IllegalArgumentException ex = assertThrows(
                IllegalArgumentException.class,
                () -> testClass.appleIsSweetAndRed("orange", "red", "sweet"));

        assertEquals("this is the exception message", ex.getMessage());
        assertEquals(NullPointerException.class, ex.getCause().getClass());
    }

通过返回异常对象本身,assertThrows() 允许您测试有关抛出异常的各个方面。

For JUnit 5 it is much easier:

    @Test
    void testAppleIsSweetAndRed() throws Exception {

        IllegalArgumentException ex = assertThrows(
                IllegalArgumentException.class,
                () -> testClass.appleIsSweetAndRed("orange", "red", "sweet"));

        assertEquals("this is the exception message", ex.getMessage());
        assertEquals(NullPointerException.class, ex.getCause().getClass());
    }

By returning the exception object itself, assertThrows() allows you to test every aspect regarding your thrown exceptions.

冷情妓 2024-07-25 02:59:53

我制作了一个与其他发布的类似的助手:

public class ExpectExceptionsExecutor {

    private ExpectExceptionsExecutor() {
    }

    public static  void execute(ExpectExceptionsTemplate e) {
        Class<? extends Throwable> aClass = e.getExpectedException();

        try {
            Method method = ExpectExceptionsTemplate.class.getMethod("doInttemplate");
            method.invoke(e);
        } catch (NoSuchMethodException e1) {


            throw new RuntimeException();
        } catch (InvocationTargetException e1) {


            Throwable throwable = e1.getTargetException();
            if (!aClass.isAssignableFrom(throwable.getClass())) {
                //  assert false
                fail("Exception isn't the one expected");
            } else {
                assertTrue("Exception captured ", true);
                return;
            }
            ;


        } catch (IllegalAccessException e1) {
            throw new RuntimeException();
        }

        fail("No exception has been thrown");
    }


}

客户端应该实现的模板

public interface ExpectExceptionsTemplate<T extends Throwable> {


    /**
     * Specify the type of exception that doInttemplate is expected to throw
     * @return
     */
    Class<T> getExpectedException();


    /**
     * Execute risky code inside this method
     * TODO specify expected exception using an annotation
     */
    public void doInttemplate();

}

客户端代码将是这样的:

@Test
    public void myTest() throws Exception {
        ExpectExceptionsExecutor.execute(new ExpectExceptionsTemplate() {
            @Override
            public Class getExpectedException() {
                return IllegalArgumentException.class;
            }

            @Override
            public void doInttemplate() {
                riskyMethod.doSomething(null);
            }
        });
     }

它看起来非常冗长,但如果您使用具有良好自动完成功能的 IDE,您只需要编写以下类型异常和被测试的实际代码。 (其余的将由 IDE 完成:D)

I made a helper similar to the other posted ones:

public class ExpectExceptionsExecutor {

    private ExpectExceptionsExecutor() {
    }

    public static  void execute(ExpectExceptionsTemplate e) {
        Class<? extends Throwable> aClass = e.getExpectedException();

        try {
            Method method = ExpectExceptionsTemplate.class.getMethod("doInttemplate");
            method.invoke(e);
        } catch (NoSuchMethodException e1) {


            throw new RuntimeException();
        } catch (InvocationTargetException e1) {


            Throwable throwable = e1.getTargetException();
            if (!aClass.isAssignableFrom(throwable.getClass())) {
                //  assert false
                fail("Exception isn't the one expected");
            } else {
                assertTrue("Exception captured ", true);
                return;
            }
            ;


        } catch (IllegalAccessException e1) {
            throw new RuntimeException();
        }

        fail("No exception has been thrown");
    }


}

And the template the client should implement

public interface ExpectExceptionsTemplate<T extends Throwable> {


    /**
     * Specify the type of exception that doInttemplate is expected to throw
     * @return
     */
    Class<T> getExpectedException();


    /**
     * Execute risky code inside this method
     * TODO specify expected exception using an annotation
     */
    public void doInttemplate();

}

And the client code would be something like this:

@Test
    public void myTest() throws Exception {
        ExpectExceptionsExecutor.execute(new ExpectExceptionsTemplate() {
            @Override
            public Class getExpectedException() {
                return IllegalArgumentException.class;
            }

            @Override
            public void doInttemplate() {
                riskyMethod.doSomething(null);
            }
        });
     }

It looks really verbose but if you use an IDE with good autocompletion you will only need to write the type of exception and the actual code under test. (the rest will be done by the IDE :D)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文