首页 > 解决方案 > Spring Boot @Retryable 模拟测试

问题描述

我有一个如下代码块:

    @Service
    ExecutorService {

    @Autowired
    IAccountService accountService;

        @Retryable(maxAttempts = 3, value = {DataIntegrityViolationException.class}, backoff = @Backoff(delay = 1000, multiplier = 2))
        public void execute(RequestDto reqDto)
        {
            Account acc = accountService.getAccount(reqDto.getAccountId);
            ...
        }
    }

在 Mockito 测试中,我只想按预期查看调用方法 3 次。

    @RunWith(SpringRunner.class)
    public class CampaignExecuterServiceTest
    {
        private static final Long ACCOUNT_ID = 1L;

        @InjectMocks
        private ExecutorService executorService;

        @Mock
        private IAccountService accountService;

        @Test
        public void execute_success()
        {
            Account account = new Account(ACCOUNT_ID, null, null, null, null);
            RequestDto reqDto = new RequestDto();
            when(accountService.getAccount(any())).thenThrow(DataIntegrityViolationException.class);
            executorService.execute(reqDto);
            verify(executorService, times(3)).execute(any());
        }
    }

测试只是抛出一个异常。但我预计它会调用它 3 次。

标签: spring-bootunit-testingmockito

解决方案


这里有几个问题。

1)@SpringBootTest需要创建一个 Spring Boot 应用程序运行程序来拦截您的可重试方法。JUnit 自己不会这样做。此外,classes参数需要是您的主类,而MainApplication不是可以作为 Spring 引导应用程序运行的类的子集。

2)ExecutorService必须带有注释,@Autowired因此它将是测试正在创建的 Spring 引导应用程序中的 bean。

3)IAccountService必须是@MockBean测试 Spring 引导环境如何知道使用模拟类ExecutorService而不是真正的 bean。

4)在测试中,第三次模拟调用需要返回一个有效的结果,否则会抛出异常,测试将失败。或者,您可以在测试中捕获异常。

5)ExecutorService不是模拟或间谍,因此verify在运行时不会将其作为 arg 接受,而是accountService模拟,因此只需断言它被调用 3 次。

另一个注意事项是,在 Spring 引导配置中的某处或MainApplication您必须有@EnableRetry注释。

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = MainApplication.class)
public class CampaignExecuterServiceTest {
    private static final Long ACCOUNT_ID = 1L;

    @Autowired
    private ExecutorService executorService;

    @MockBean
    private IAccountService accountService;

    @Test
    public void execute_success() {
        Account account = new Account(ACCOUNT_ID, null, null, null, null);;
        RequestDto reqDto = new RequestDto();
        when(accountService.getAccount(any()))
                .thenThrow(DataIntegrityViolationException.class)
                .thenThrow(DataIntegrityViolationException.class)
                .thenReturn(account);
        executorService.execute(reqDto);
        verify(accountService, times(3)).getAccount(any());
    }
}

推荐阅读