You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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网关
  • 运行其他服务

解决方案

问题根源

  1. Mock逻辑错误:直接对restTemplate.exchange(...).getBody()做Mock,会导致exchange方法本身返回null,调用getBody()时触发空指针异常。
  2. 验证逻辑不匹配:测试中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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 15:30:55