使用SpringRunner测试Spring-Retry时@Autowired Bean为空求助
问题:SpringRunner测试中@Autowired注入Bean为空且@Retryable重试逻辑验证失败
使用SpringRunner为带有Spring Retry注解的DeltaHelper类编写单元测试时,出现两个问题:
- @Autowired注入的DeltaHelper实例为空
- 尝试验证重试逻辑时无法得到预期结果
曾尝试使用MockitoJUnitRunner,但@Retryable注解仅在Spring环境下才能生效,因此必须使用SpringRunner。
测试类代码
@RunWith(SpringRunner.class) @ContextConfiguration public class DeltaHelperTest { @Autowired private DeltaHelper deltaHelper; @Before public void setUp() { System.setProperty("delta.process.retries", "2"); } @After public void validate() { validateMockitoUsage(); } @Test public void retriesAfterOneFailAndThenPass() throws Exception { when(deltaHelper.restService.call(any(), any())).thenThrow(new HttpException()); deltaHelper.process(any(),any()); verify(deltaHelper, times(2)).process(any(), any()); } @Configuration @EnableRetry @EnableAspectJAutoProxy(proxyTargetClass=true) @Import(MockitoSkipAutowireConfiguration.class) public static class Application { @Bean public DeltaHelper deltaHelper() { DeltaHelper deltaHelper = new DeltaHelper(); deltaHelper.myStorageService= myStorageService(); deltaHelper.restService = restService(); return deltaHelper; } @Bean public MyStorageService myStorageService() { return new MyStorageService(); } @Bean public MyRestService restService() { return new MyRestService(); } @Bean public MyRepo myRepository() { return mock(MyRepo.class); } } @Configuration public static class MockitoSkipAutowireConfiguration { @Bean MockBeanFactory mockBeanFactory() { return new MockBeanFactory(); } private static class MockBeanFactory extends InstantiationAwareBeanPostProcessorAdapter { @Override public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { return !mockingDetails(bean).isMock(); } } } }
相关业务类代码
@Component public class DeltaHelper { @Autowired MyRestService restService; @Autowired MyStorageService myStorageService; @NotNull @Retryable( value = Exception.class, maxAttemptsExpression = "${delta.process.retries}" ) public String process(String api, HttpEntity<?> entity) { return restService.call(api, entity); } @Recover public String recover(Exception e, String api, HttpEntity<?> entity) { myStorageService.save(api); return "recover"; } } @Service public class MyStorageService { @Autowired MyRepo myRepo; @Async public MyEntity save(String api) { return myRepo.save(new MyEntity(api, System.currentTimeMillis())); } } public class MyRestService extends org.springframework.web.client.RestTemplate { }
问题分析
@Autowired注入为空:
- 测试类中手动
new DeltaHelper()并设置依赖,没有让Spring通过依赖注入机制管理该Bean,导致Spring无法为其创建代理对象,同时可能因为Bean创建方式不符合Spring规范,导致@Autowired注入失败。 MockitoSkipAutowireConfiguration中的MockBeanFactory会阻止Mock对象的后续处理,干扰了Spring对Bean的正常初始化流程。
- 测试类中手动
重试逻辑验证失败:
- 手动实例化的DeltaHelper没有被Spring的Retry切面代理,@Retryable注解完全不生效,自然无法触发重试。
- 测试中错误地验证
deltaHelper.process()的调用次数,实际上重试是由Spring切面拦截后重复调用方法内部逻辑,而非外部重复调用process方法,应该验证依赖的restService.call()的调用次数。
解决方案
1. 正确配置Spring上下文,使用@MockBean模拟依赖
改用@SpringBootTest(如果是Spring Boot项目)或调整@ContextConfiguration,让Spring管理Bean的创建和代理,同时用@MockBean模拟MyRestService等依赖,避免手动实例化。
2. 调整测试逻辑,验证正确的对象
验证restService.call()的调用次数是否符合重试配置,而不是DeltaHelper的process方法。
修改后的测试类示例
@RunWith(SpringRunner.class) @SpringBootTest(classes = {DeltaHelper.class, MyStorageService.class}) @EnableRetry @EnableAsync // 因为MyStorageService有@Async方法,需要开启异步支持 public class DeltaHelperTest { @Autowired private DeltaHelper deltaHelper; @MockBean private MyRestService restService; @MockBean private MyRepo myRepo; @Before public void setUp() { System.setProperty("delta.process.retries", "2"); } @Test public void retriesAfterOneFailAndThenPass() throws Exception { // 第一次调用抛异常,触发重试 when(restService.call(anyString(), any(HttpEntity.class))) .thenThrow(new HttpException()) .thenReturn("success"); String result = deltaHelper.process("test-api", new HttpEntity<>(null)); // 验证restService被调用2次(1次失败+1次重试成功) verify(restService, times(2)).call(anyString(), any(HttpEntity.class)); assertEquals("success", result); } @Test public void triggersRecoverAfterAllRetriesFail() throws Exception { // 所有重试都抛异常,触发@Recover方法 when(restService.call(anyString(), any(HttpEntity.class))) .thenThrow(new HttpException()); String result = deltaHelper.process("test-api", new HttpEntity<>(null)); // 验证restService被调用2次(重试次数maxAttempts=2) verify(restService, times(2)).call(anyString(), any(HttpEntity.class)); // 验证存储服务的save方法被调用 verify(myRepo, times(1)).save(any(MyEntity.class)); assertEquals("recover", result); } }
额外注意事项
- MyStorageService中的@Async方法需要在测试类或配置类中添加
@EnableAsync才能生效,否则会同步执行。 - 如果不是Spring Boot项目,可以用
@ContextConfiguration指定包含@EnableRetry、@EnableAsync的配置类,替代@SpringBootTest。
内容的提问来源于stack exchange,提问作者mkdir 404
相关产品推荐
相关产品推荐

