Spring Boot微服务JUnit测试RestTemplate空指针异常问题排查
问题
在Spring Boot微服务中运行JUnit测试时遇到问题:已从/authenticate/login接口获取有效的Bearer Token,通过Postman可正常调用任意服务,但将Token复制到测试方法中后,测试无法正常执行。
测试代码
@DisplayName("Get Order - Success Scenario") @Test void test_When_Order_Success() { String bearerToken = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJVc2VyIiwiaXNzIjoiUk9MRV9VU0VSICIsImlhdCI6MTY3MTQ4ODgyMiwiZXhwIjoxNjcxNDg4OTQyfQ.g83kKmFzDH539ZcpxM9D8bE_famFOevkOqNst_E8YG07b4yR4cEqcrySvz36vw8GJxTKm9gUQIM1J_G8cC5RGQ"; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.set("Authorization", "Bearer " + bearerToken); HttpEntity<String> request = new HttpEntity<String>(headers); //Mocking Order order = getMockOrder(); when(orderRepository.findById(anyLong())) .thenReturn(Optional.of(order)); when(restTemplate.exchange( "http://PRODUCT-SERVICE/product/" + order.getProductId(), HttpMethod.GET, request, ProductResponse.class).getBody()).thenReturn(getMockProductResponse()); when(restTemplate.exchange( "http://PAYMENT-SERVICE/payment/order/" + order.getId(), HttpMethod.GET, request, PaymentResponse.class).getBody()).thenReturn(getMockPaymentResponse()); //Actual OrderResponse orderResponse = orderService.getOrderDetails(1,bearerToken); //Verification verify(orderRepository, times(1)).findById(anyLong()); verify(restTemplate, times(1)).getForObject( "http://PRODUCT-SERVICE/product/" + order.getProductId(), ProductResponse.class); verify(restTemplate, times(1)).getForObject( "http://PAYMENT-SERVICE/payment/order/" + order.getId(), PaymentResponse.class); //Assert assertNotNull(orderResponse); assertEquals(order.getId(), orderResponse.getOrderId()); }
错误信息
java.lang.NullPointerException: Cannot invoke "org.springframework.http.ResponseEntity.getBody()" because the return value of "org.springframework.web.client.RestTemplate.exchange(String, org.springframework.http.HttpMethod, org.springframework.http.HttpEntity, java.lang.Class, Object[])" is null
项目启动步骤
- 运行服务注册中心(Eureka Server)
- 运行配置中心
- 通过Docker执行以下命令启动zipkin和redis:
docker run -d -p 9411:9411 openzipkin/zipkin docker run -d --name redis -p 6379:6379 redis - 运行API网关
- 运行其他服务
解决方案
问题根源
- Mock逻辑错误:直接对
restTemplate.exchange(...).getBody()做Mock,会导致exchange方法本身返回null,调用getBody()时触发空指针异常。 - 验证逻辑不匹配:测试中Mock的是
restTemplate.exchange方法,但验证时检查的是restTemplate.getForObject的调用次数,两者不对应。
修复步骤
1. 修正RestTemplate的Mock逻辑
先Mockexchange方法返回一个非空的ResponseEntity,再指定其getBody()的返回值:
// Mock PRODUCT-SERVICE调用 ProductResponse mockProduct = getMockProductResponse(); when(restTemplate.exchange( "http://PRODUCT-SERVICE/product/" + order.getProductId(), HttpMethod.GET, request, ProductResponse.class)) .thenReturn(new ResponseEntity<>(mockProduct, HttpStatus.OK)); // Mock PAYMENT-SERVICE调用 PaymentResponse mockPayment = getMockPaymentResponse(); when(restTemplate.exchange( "http://PAYMENT-SERVICE/payment/order/" + order.getId(), HttpMethod.GET, request, PaymentResponse.class)) .thenReturn(new ResponseEntity<>(mockPayment, HttpStatus.OK));
2. 修正验证逻辑
确保验证的方法与实际调用一致,将验证getForObject改为验证exchange:
verify(restTemplate, times(1)).exchange( "http://PRODUCT-SERVICE/product/" + order.getProductId(), HttpMethod.GET, request, ProductResponse.class); verify(restTemplate, times(1)).exchange( "http://PAYMENT-SERVICE/payment/order/" + order.getId(), HttpMethod.GET, request, PaymentResponse.class);
3. 确认Token传递(可选)
检查orderService.getOrderDetails内部是否正确将传入的bearerToken构造到请求头中,确保与测试中构造的request对象一致。
内容的提问来源于stack exchange,提问作者Sercan Noyan Germiyanoğlu
相关产品推荐
相关产品推荐

