使用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; } }
问题核心分析
- 最初的NPE问题:是因为
GoogleAccessor的client字段没有被Mockito正确注入模拟对象,你调整字段初始化和引入正确的org.mockito.Mock依赖后解决了这个问题,但还有更关键的隐藏问题。 - 模拟响应不生效:你在
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
相关产品推荐
相关产品推荐

