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

eoxn13cs  于 9个月前  发布在  其他
关注(0)|答案(1)|浏览(114)

我在我的一个服务方法中调用@Async方法,现在我试图模拟当@Async方法在后台抛出异常时的情况,以Assert调用者方法继续正常执行,尽管异步抛出异常。
最初在测试中,我尝试模拟抛出的异常:

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

但在这种情况下,调用方方法的行为就好像运行时异常是在主线程中抛出的,而不是异步的。
Mockito是否有办法指定thenThrow(...)中的异常应该异步抛出,而不是在主线程中抛出?
UPD。这是一个类,在这个类中调用了autowired bean的方法:

@Log4j2
@Service
@Setter
@ConfigurationProperties(prefix = "spring.profiles")
public class MyService {

  @Autowired private MyAsyncUtil asyncUtil;

  public void myMethod1() {
    myMethod2();    
  }

  public void myMethod2() {
    asyncUtil.myAsyncMethod();
  }       

}

这是MyAsyncUtil类,方法注解为@Async

@Component
@Log4j2
public class MyAsyncUtil {

  @Async
  public Void myAsyncMethod() {
    // do something
  } 

}
5q4ezhmt

5q4ezhmt1#

@Async是一个Spring框架注解-如果方法调用在Spring上下文内(Spring beans之间)执行,则该方法可能会被拦截并在与原始调用者线程不同的线程上运行。如果在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这样的库来避免重复测试(主线程可能在重复测试线程之前完成运行)。注意:assertThatNoExceptionAssertJ library的一个方法。
在测试中模拟异步执行的另一种方法可以简单地自己创建新线程,模仿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在“幕后”是如何工作的。
我已经准备好了a GitHub repository-我验证了这两个测试,他们通过。

相关问题