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

使用Mockito模拟Apache HTTP Client时遇到的问题

解决Mockito模拟HTTP GET请求时的NPE及模拟响应不生效问题

我刚梳理了你的问题,作为Mockito新手,你遇到的这两个问题其实非常典型——先是空指针异常(NPE),然后模拟响应不生效,核心原因都和GoogleAccessor里的对象初始化逻辑有关,咱们一步步来解决:

问题回顾

你想要测试GoogleAccessor的HTTP GET请求逻辑,最初因mClient未初始化抛出NPE,调整字段初始化和引入正确依赖后NPE解决,但模拟的响应始终没有输出。先把你的原始代码贴出来方便理解:

原GoogleAccessor工具类

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
public class GoogleAccessor {
private HttpClient client ;
public void getGoogle() throws ClientProtocolException, IOException {
String url = "http://www.google.com/search?q=httpClient";
client = new DefaultHttpClient(); // 这里是核心问题所在!
HttpGet request = new HttpGet(url);
try {
HttpResponse response = client.execute(request);
BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
StringBuffer result = new StringBuffer();
String line = "";
while ((line = rd.readLine()) != null) {
result.append(line);
}
System.out.println(result);
} catch (Exception e) {
System.out.println(e);
}
}
}

原JUnit测试类

import static org.mockito.Mockito.when;
import org.apache.http.ProtocolVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicStatusLine;
import org.mockito.Mock;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
public class TestTest {
@Mock
private HttpClient mClient;
@InjectMocks
private GoogleAccessor dummy;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
@Test
public void testAddCustomer_returnsNewCustomer() throws Exception {
when(mClient.execute(Mockito.any())).thenReturn(buildResponse(201, "OK", "{\"message\":\"Model was stored\"}"));
dummy.getGoogle();
}
BasicHttpResponse buildResponse(int statusCode, String reason, String body) {
BasicHttpResponse response = new BasicHttpResponse(
new BasicStatusLine(new ProtocolVersion("HTTP", 1, 0), statusCode, reason));
if (body != null) {
response.setEntity(new ByteArrayEntity(body.getBytes()));
}
return response;
}
}

问题核心分析

  1. 最初的NPE问题:是因为GoogleAccessor的client字段没有被Mockito正确注入模拟对象,你调整字段初始化和引入正确的org.mockito.Mock依赖后解决了这个问题,但还有更关键的隐藏问题。
  2. 模拟响应不生效:你在getGoogle()方法里写了client = new DefaultHttpClient();,这会直接覆盖Mockito通过@InjectMocks注入的模拟对象!也就是说,你测试代码里配置的Mock逻辑完全没用,实际执行的是真实的HTTP请求,自然看不到模拟的响应内容。

解决方案

第一步:重构GoogleAccessor,支持依赖注入

把client的实例化逻辑移到构造函数中,让测试时可以灵活注入Mock对象,避免在方法内覆盖已注入的实例:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

public class GoogleAccessor {
    private HttpClient client;

    // 无参构造,用于正常业务场景初始化真实的HttpClient
    public GoogleAccessor() {
        this.client = new DefaultHttpClient();
    }

    // 带参构造,专门给测试用,注入Mock的HttpClient
    public GoogleAccessor(HttpClient client) {
        this.client = client;
    }

    public void getGoogle() throws ClientProtocolException, IOException {
        String url = "http://www.google.com/search?q=httpClient";
        // 删掉原来的client = new DefaultHttpClient(); 这一行!
        HttpGet request = new HttpGet(url);
        try {
            HttpResponse response = client.execute(request);
            BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
            StringBuffer result = new StringBuffer();
            String line = "";
            while ((line = rd.readLine()) != null) {
                result.append(line);
            }
            System.out.println(result);
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

第二步:调整测试类,确保Mock对象被正确注入

你可以选择两种方式注入Mock对象,推荐第一种(更清晰可控):

方式一:构造函数手动注入Mock

import static org.mockito.Mockito.when;
import org.apache.http.ProtocolVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicStatusLine;
import org.mockito.Mock;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

public class GoogleAccessorTest {
    @Mock
    private HttpClient mClient;
    private GoogleAccessor dummy;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        // 通过带参构造注入Mock的HttpClient
        dummy = new GoogleAccessor(mClient);
    }

    @Test
    public void testGetGoogle_returnsMockResponse() throws Exception {
        // 明确匹配HttpGet类型,避免过度匹配
        when(mClient.execute(Mockito.any(HttpGet.class)))
                .thenReturn(buildResponse(201, "OK", "{\"message\":\"Model was stored\"}"));
        
        dummy.getGoogle();
        // 现在运行测试,就能看到模拟的响应输出了
    }

    BasicHttpResponse buildResponse(int statusCode, String reason, String body) {
        BasicHttpResponse response = new BasicHttpResponse(
                new BasicStatusLine(new ProtocolVersion("HTTP", 1, 0), statusCode, reason));
        if (body != null) {
            response.setEntity(new ByteArrayEntity(body.getBytes()));
        }
        return response;
    }
}

方式二:继续使用@InjectMocks

如果你想保留@InjectMocks,只要确保GoogleAccessor里不再重新赋值client,Mockito会自动注入Mock对象,测试类可以和你原来的结构类似,只需把测试方法名改得更贴合场景(比如testGetGoogle_returnsMockResponse)。

第三步:添加断言验证(可选但推荐)

单纯依赖打印输出验证结果不够可靠,你可以捕获System.out的输出,用断言明确验证模拟响应是否被正确处理:

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import org.junit.After;
import org.junit.Before;
// 其他import保持不变

public class GoogleAccessorTest {
    private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
    private final PrintStream originalOut = System.out;

    @Before
    public void setUpStreams() {
        // 替换System.out,用于捕获输出内容
        System.setOut(new PrintStream(outContent));
    }

    @After
    public void restoreStreams() {
        // 测试结束后恢复原始的System.out
        System.setOut(originalOut);
    }

    @Test
    public void testGetGoogle_returnsMockResponse() throws Exception {
        String expectedResponse = "{\"message\":\"Model was stored\"}";
        when(mClient.execute(Mockito.any(HttpGet.class)))
                .thenReturn(buildResponse(201, "OK", expectedResponse));
        
        dummy.getGoogle();
        
        // 断言输出包含我们模拟的响应内容
        assert(outContent.toString().contains(expectedResponse));
    }
}

关键总结

  • 禁止在待测试方法内重新实例化被Mock的对象:这会直接覆盖Mockito注入的模拟实例,导致所有Mock逻辑失效。
  • 依赖注入是Mockito生效的基础:通过构造函数、Setter或字段注入,让Mock对象能被正确传递到待测试类中。
  • 用断言替代人工检查输出:断言能让测试结果更明确,避免依赖主观判断。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:58:19