Spring Boot - 如何在 Mockito 中测试 @Async 方法抛出异常

问题描述 投票:0回答:1

我正在我的一个服务方法中调用

@Async
方法,现在我尝试模拟
@Async
方法在后台抛出异常的情况,以断言调用者方法继续正常执行,尽管异步抛出异常。

最初在测试中,我尝试模拟抛出的异常:

when(myAsyncServiceMock.myAsyncMethod(...some arguments...).thenThrow(new RuntimeException("failed to complete asynchronous method"));

但在这种情况下,调用者方法的行为就像在主线程中抛出运行时异常,而不是异步抛出。

Mockito 是否有办法指定

thenThrow(...)
中的异常应该异步抛出,而不是在主线程中?

java spring-boot asynchronous testing mockito
1个回答
0
投票

@Async
是一个Spring框架注释 - 如果方法调用是在Spring上下文中(Spring bean之间)执行的,则该方法可能会被拦截并在与原始调用者的线程不同的线程上运行。如果在 Spring 上下文之外调用该方法,则忽略
@Async
注释,并且该方法将在与调用者相同的线程上执行。

您可以编写一个涉及 Spring 的测试并验证如下行为:

@SpringBootTest
class TestedClassSpringTest {

    @MockBean
    SomeService someService;
    @Autowired
    TestedClass testedClass;

    @Test
    void test() {
        var exception = new RuntimeException("test error");
        when(someService.doStuff())
                .thenThrow(exception);

        ThrowingCallable methodCall = () -> testedClass.execute();

        assertThatNoException()
                .isThrownBy(methodCall);
    }
}

您将在控制台中看到异常实际上被抛出并记录,但原始调用并没有因为异常而中断,因为它在另一个线程上运行。如果需要,您还可以验证是否调用了

AsyncUncaughtExceptionHandler
(使用
Mockito.verify
方法),但在这种情况下,您必须记住调用是在与主线程不同的线程上执行的,因此使用可能需要诸如Awaitility之类的库来避免片状测试(主线程可能在异步线程之前完成运行)。 注意:
assertThatNoException
是来自AssertJ库的方法。

在测试中模拟异步执行的另一种方法可以简单地自己创建新线程,模仿 Spring 行为:

class TestedClassVanillaTest {

    SomeService someService = mock(SomeService.class);
    TestedClass testedClass = new TestedClass(someService);

    @Test
    void test() {
        var exception = new RuntimeException("test error");
        when(someService.doStuff())
                .thenThrow(exception);

        ThrowableAssert.ThrowingCallable methodCall = () -> {
            new Thread(() -> testedClass.execute())
                    .start();
        };

        assertThatNoException()
                .isThrownBy(methodCall);
    }
}

这种方法的缺点是,不需要

@Async
注释即可工作 - 如果将来将其删除,测试仍然会通过。我建议不要使用这种方法,因为它只是测试一个事实:在不同线程中抛出的异常不会被主线程捕获,这是 Java 的老生常谈。尽管如此 - 这些测试仍然有助于理解 Spring 如何“在幕后”做事。


我已经准备了一个 GitHub 存储库 - 我验证了这两个测试并且它们通过了。

© www.soinside.com 2019 - 2024. All rights reserved.