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

使用Mockito测试Micronaut HttpClient(Java)时遇空指针异常

问题:Mock HttpClient.toBlocking().exchange()时触发空指针异常

尝试测试服务类方法行为,需要Mock HttpClient.toBlocking().exchange()的返回值,但使用when-thenReturn设置时抛出空指针异常:

java.lang.NullPointerException: Cannot invoke "io.micronaut.http.client.BlockingHttpClient.exchange(io.micronaut.http.HttpRequest, java.lang.Class)" because the return value of "io.micronaut.http.client.HttpClient.toBlocking()" is null

测试代码如下:

@MicronautTest
public class WeatherServiceTest {

static final String HOUSE_NUM = "testNum";
static final String STREET = "testStreet";
static final String POST_CODE = "testPostCode";
static final String CITY = "testCity";

@Mock
HttpClient httpClient;

@Test
public void whenFetchingWeather() {
    when(httpClient.toBlocking().exchange(HttpRequest.GET("https://api.weatherapi.com/v1/current.json?key=XXX&q=city&aqi=no"), WeatherResponse.class))
            .thenReturn(createWeatherResponse());

    WeatherService weatherService = new WeatherService();
    HttpResponse<WeatherResponse> response = weatherService.fetchWeather(HOUSE_NUM, STREET, POST_CODE, CITY);

    assertEquals(HttpStatus.OK, response.getStatus());
}


public static HttpResponse<WeatherResponse> createWeatherResponse() {
    WeatherResponse.Condition condition = new WeatherResponse.Condition();
    condition.setCode("1003");

    WeatherResponse.CurrentWeather currentWeather = new WeatherResponse.CurrentWeather();
    currentWeather.setCondition(condition);
    currentWeather.setWind("3.6");
    currentWeather.setTemperature("2.0");

    WeatherResponse weatherResponse = new WeatherResponse();
    weatherResponse.setCurrentWeather(currentWeather);

    return HttpResponse.ok(weatherResponse);
}
}

解决方案

问题根源

只Mock了HttpClient实例,但未Mock它的toBlocking()方法返回的BlockingHttpClient对象,导致调用httpClient.toBlocking()时返回null,触发空指针异常。另外,直接new WeatherService()无法将Mock的HttpClient注入到服务类中,服务类实际使用的可能是未初始化的HttpClient实例。

修复步骤

  • 添加BlockingHttpClient的Mock实例,让httpClient.toBlocking()返回这个Mock对象
  • 调整Mock顺序:先MocktoBlocking()的返回值,再Mockexchange()方法
  • 通过构造器或依赖注入将Mock的HttpClient传入WeatherService

修复后的测试代码

@MicronautTest
public class WeatherServiceTest {

static final String HOUSE_NUM = "testNum";
static final String STREET = "testStreet";
static final String POST_CODE = "testPostCode";
static final String CITY = "testCity";

@Mock
HttpClient httpClient;
@Mock
BlockingHttpClient blockingHttpClient;

@Test
public void whenFetchingWeather() {
    // 先Mock toBlocking()返回Mock的BlockingHttpClient
    when(httpClient.toBlocking()).thenReturn(blockingHttpClient);
    
    // 再Mock exchange方法的返回值
    HttpRequest<?> request = HttpRequest.GET("https://api.weatherapi.com/v1/current.json?key=XXX&q=city&aqi=no");
    when(blockingHttpClient.exchange(request, WeatherResponse.class))
            .thenReturn(createWeatherResponse());

    // 将Mock的httpClient传入WeatherService(假设服务类支持构造器注入)
    WeatherService weatherService = new WeatherService(httpClient);
    HttpResponse<WeatherResponse> response = weatherService.fetchWeather(HOUSE_NUM, STREET, POST_CODE, CITY);

    assertEquals(HttpStatus.OK, response.getStatus());
}


public static HttpResponse<WeatherResponse> createWeatherResponse() {
    WeatherResponse.Condition condition = new WeatherResponse.Condition();
    condition.setCode("1003");

    WeatherResponse.CurrentWeather currentWeather = new WeatherResponse.CurrentWeather();
    currentWeather.setCondition(condition);
    currentWeather.setWind("3.6");
    currentWeather.setTemperature("2.0");

    WeatherResponse weatherResponse = new WeatherResponse();
    weatherResponse.setCurrentWeather(currentWeather);

    return HttpResponse.ok(weatherResponse);
}
}

额外说明

  • 如果WeatherService使用字段注入而非构造器注入,可通过@Inject或测试框架注入方式将Mock的HttpClient注入服务实例,避免直接new对象
  • 建议使用参数匹配器(如any(HttpRequest.class)、any(Class.class))提高Mock灵活性,避免因请求URL或参数细微变化导致Mock失效

内容的提问来源于stack exchange,提问作者Artishock

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 01:28:13