如何为带有Spring Retry注解的Feign Client编写重试测试?
测试带Spring Retry的Feign Client重试逻辑
你定义了带有@Retryable注解的Feign Client接口,尝试用@SpringBootTest结合Mockito的verify测试重试时,直接Mock MyClient接口的方式无法验证真实的重试逻辑——因为Spring Retry是通过AOP代理实现的,只有容器生成的真实Feign代理实例才会携带重试逻辑,Mock的接口不会触发重试。
以下是两种可行的测试方案:
方案一:用WireMock模拟服务端错误(贴近真实场景)
这种方式通过模拟服务端返回HTTP错误,触发Feign的异常转换,进而触发Spring Retry的重试逻辑,最接近生产环境的真实调用场景。
步骤:
- 添加WireMock依赖(以Maven为例):
<dependency> <groupId>com.github.tomakehurst</groupId> <artifactId>wiremock-jre8-spring-boot-starter</artifactId> <version>2.35.0</version> <scope>test</scope> </dependency>
- 编写测试类:
import com.github.tomakehurst.wiremock.client.WireMock; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; import org.springframework.test.context.TestPropertySource; @SpringBootTest @AutoConfigureWireMock(port = 0) // 随机端口启动WireMock @TestPropertySource(properties = { "feign.client.config.my-client.url=http://localhost:${wiremock.server.port}", "spring.retry.backoff.delay=0", // 关闭退避等待,加快测试速度 "spring.retry.backoff.multiplier=1" }) public class MyClientRetryTest { @Autowired private MyClient myClient; @Test void testPerformACallRetriesOnFailure() { // 模拟服务端返回500错误 WireMock.stubFor(WireMock.post(WireMock.urlEqualTo("")) .willReturn(WireMock.serverError())); // 调用方法(预期会抛出FeignException,因为最终重试3次后仍失败) try { myClient.performACall(new SomeBody()); } catch (Exception ignored) { // 忽略最终的失败异常,只关注重试次数 } // 验证WireMock收到了3次请求(对应maxAttempts=3) WireMock.verify(3, WireMock.postRequestedFor(WireMock.urlEqualTo(""))); } }
方案二:Mock Feign的ErrorDecoder(轻量单元测试)
如果不想启动完整的HTTP服务,可以通过Mock Feign的ErrorDecoder组件,强制抛出触发重试的异常,验证重试逻辑。
步骤:
编写测试类:
import feign.FeignException; import feign.codec.ErrorDecoder; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.TestPropertySource; @SpringBootTest @TestPropertySource(properties = { "spring.retry.backoff.delay=0", "spring.retry.backoff.multiplier=1" }) public class MyClientRetryMockTest { @Autowired private MyClient myClient; @MockBean private ErrorDecoder errorDecoder; @Test void testPerformACallRetriesOnFailure() { // 配置ErrorDecoder每次调用都抛出FeignException Mockito.when(errorDecoder.decode(Mockito.anyString(), Mockito.any())) .thenThrow(FeignException.internalServerError("Mock error", null)); // 调用方法 try { myClient.performACall(new SomeBody()); } catch (Exception ignored) { } // 验证ErrorDecoder被调用了3次(对应重试3次) Mockito.verify(errorDecoder, Mockito.times(3)) .decode(Mockito.anyString(), Mockito.any()); } }
关键注意事项:
- 重试触发条件:默认
@Retryable会对所有RuntimeException重试,Feign的FeignException属于RuntimeException,所以无需额外配置;如果需要指定特定异常,可在@Retryable(include = {FeignException.class})中声明。 - 关闭退避等待:测试时设置
spring.retry.backoff.delay=0可以避免因退避策略导致的测试等待时间过长。 - 避免Mock接口本身:必须注入Spring容器生成的真实MyClient代理实例,而非直接Mock该接口,否则重试逻辑不会生效。
内容的提问来源于stack exchange,提问作者Chanandler Bong
相关产品推荐
相关产品推荐

