Spring Mockito测试RestTemplate.postForEntity抛出URI非绝对路径异常
问题根本原因
你的MyServiceImpl类中postCar方法每次执行时都会手动new RestTemplate()生成全新的实例,完全没有使用单元测试中Mock好并注入到Service的restTemplateMock,因此你定义的Mock规则全部不生效,代码仍然会用新生成的RestTemplate发起真实网络请求,自然会依赖外部资源、不符合单元测试的隔离要求。
修复步骤
- 第一步:修改Service代码,将RestTemplate改为注入的Bean,禁止在方法内手动创建
@Service public class MyServiceImpl implements MyService { @Value("${myUri}") private String uri; // 改为注入RestTemplate,不要在方法中new @Autowired private RestTemplate restTemplate; public void setUri(String uri) { this.uri = uri; } // 新增set方法方便单元测试注入Mock实例 public void setRestTemplate(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @Override public ResponseEntity<CarResponse> postCar(CarRequest carRequest, HttpHeaders httpHeaders) { List<String> authHeader = httpHeaders.get("authorization"); HttpHeaders headers = new HttpHeaders(); headers.add("Authorization", authHeader.get(0)); HttpEntity<CarRequest> request = new HttpEntity<>(carRequest, headers); // 使用注入的restTemplate发起请求 ResponseEntity<CarResponse> carResponse = restTemplate.postForEntity(uri, request, CarResponse.class); return carResponse; } }
- 第二步:修正单元测试代码的笔误与逻辑问题
public class CarServiceTest { @InjectMocks MyServiceImpl carServiceSut; @Mock RestTemplate restTemplateMock; CarResponse carResponseFake = new CarResponse(); // 提前初始化测试用的请求对象 CarRequest carRequestFake = new CarRequest(); @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); carResponseFake.setCarVin(12345); // 统一注入测试用的假URI carServiceSut.setUri("http://FAKE/URI/myapi/cars"); } @Test void testPostCar() { // Arrange HttpHeaders headers = new HttpHeaders(); headers.add("Authorization", "test-token"); ResponseEntity<CarResponse> expectedResponse = new ResponseEntity<>(carResponseFake, HttpStatus.OK); when(restTemplateMock.postForEntity( eq("http://FAKE/URI/myapi/cars"), Mockito.any(HttpEntity.class), eq(CarResponse.class) )).thenReturn(expectedResponse); // Act ResponseEntity<CarResponse> actualResponse = carServiceSut.postCar(carRequestFake, headers); // Assert assertEquals(12345, actualResponse.getBody().getCarVin()); // 额外验证RestTemplate的方法确实被调用了1次,确认Mock生效 Mockito.verify(restTemplateMock, Mockito.times(1)).postForEntity( eq("http://FAKE/URI/myapi/cars"), Mockito.any(HttpEntity.class), eq(CarResponse.class) ); } }
额外优化建议
- 可以在Spring配置类中注册全局的RestTemplate Bean,避免重复创建实例,也方便统一配置超时时间、拦截器等公共参数
- 修复代码中现存的笔误:比如Service方法返回值拼写错误
cardResponse、Controller调用的getCard方法和Service实际的postCar方法不匹配等问题,避免运行时报错 - 调整后的单元测试完全不需要加载Spring上下文,纯Mockito运行速度极快,也不会依赖任何外部资源和配置文件
内容的提问来源于stack exchange,提问作者pixel
相关产品推荐
相关产品推荐

