编程入门者求助:如何用Mockito模拟Unirest的HttpResponse对象
模拟Unirest响应进行单元测试的方案
刚入门Mockito确实会遇到静态方法模拟或者构造假响应的问题,我来一步步帮你解决:
一、优先推荐:封装HTTP请求(更易测试的实践)
直接模拟静态的Unirest.get()虽然可行,但更好的做法是把HTTP调用逻辑封装成一个独立的服务类,这样既符合依赖注入的思想,也让测试更简单。
步骤1:创建HTTP服务封装类
先写一个专门处理HTTP请求的类,把Unirest的调用逻辑移进去:
import kong.unirest.HttpResponse; import kong.unirest.JsonNode; import kong.unirest.UnirestException; public class ActivityHttpService { public HttpResponse<JsonNode> fetchActivities() throws UnirestException { return Unirest.get("http://111.111.111.111:8080/activity") .header("accept", "application/json") .asJson(); } }
步骤2:修改原类,注入这个服务
把原来的getActivites()方法所在的类,改成依赖注入ActivityHttpService:
import kong.unirest.HttpResponse; import kong.unirest.JsonNode; import org.json.JSONArray; public class ActivityService { private final ActivityHttpService httpService; // 构造注入,方便测试时传入模拟对象 public ActivityService(ActivityHttpService httpService) { this.httpService = httpService; } public JSONArray getActivites() { HttpResponse<JsonNode> jsonResponse = null; try { jsonResponse = httpService.fetchActivities(); } catch (UnirestException e) { System.out.println("Server is unreachable"); } // 这里加个非空判断,避免空指针异常 if (jsonResponse == null) { return new JSONArray(); } return jsonResponse.getBody().getArray(); } }
步骤3:用Mockito模拟这个服务类
现在测试的时候,就可以轻松模拟ActivityHttpService的返回值了,不用管静态的Unirest:
import kong.unirest.HttpResponse; import kong.unirest.JsonNode; import org.json.JSONArray; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.when; public class ActivityServiceTest { @Test void testGetActivities() throws Exception { // 1. 创建模拟的HTTP服务 ActivityHttpService mockHttpService = Mockito.mock(ActivityHttpService.class); // 2. 构造假的JsonNode和HttpResponse JSONArray fakeActivities = new JSONArray("[{\"id\":1},{\"id\":2}]"); JsonNode fakeJsonNode = Mockito.mock(JsonNode.class); when(fakeJsonNode.getArray()).thenReturn(fakeActivities); HttpResponse<JsonNode> fakeResponse = Mockito.mock(HttpResponse.class); when(fakeResponse.getBody()).thenReturn(fakeJsonNode); // 3. 让模拟服务返回假响应 when(mockHttpService.fetchActivities()).thenReturn(fakeResponse); // 4. 初始化要测试的类,传入模拟对象 ActivityService activityService = new ActivityService(mockHttpService); // 5. 调用方法并验证结果 JSONArray result = activityService.getActivites(); assertEquals(2, result.length()); assertEquals(1, result.getJSONObject(0).getInt("id")); } }
二、直接模拟Unirest静态方法(适合不想重构的场景)
如果你暂时不想重构代码,想用Mockito直接模拟静态的Unirest.get(),需要用Mockito的mockStatic方法(要求Mockito版本3.4.0+):
import kong.unirest.GetRequest; import kong.unirest.HttpResponse; import kong.unirest.JsonNode; import org.json.JSONArray; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; import static org.junit.jupiter.api.Assertions.assertEquals; public class ActivityServiceTest { @Test void testGetActivitiesWithStaticMock() throws Exception { // 1. 构造假的响应链:GetRequest -> 带header的请求 -> 假的HttpResponse GetRequest mockGetRequest = Mockito.mock(GetRequest.class); when(mockGetRequest.header("accept", "application/json")).thenReturn(mockGetRequest); JSONArray fakeActivities = new JSONArray("[{\"id\":3},{\"id\":4}]"); JsonNode fakeJsonNode = Mockito.mock(JsonNode.class); when(fakeJsonNode.getArray()).thenReturn(fakeActivities); HttpResponse<JsonNode> fakeResponse = Mockito.mock(HttpResponse.class); when(fakeResponse.getBody()).thenReturn(fakeJsonNode); when(mockGetRequest.asJson()).thenReturn(fakeResponse); // 2. 模拟Unirest的静态get方法 try (MockedStatic<kong.unirest.Unirest> mockedUnirest = Mockito.mockStatic(kong.unirest.Unirest.class)) { mockedUnirest.when(() -> kong.unirest.Unirest.get("http://111.111.111.111:8080/activity")) .thenReturn(mockGetRequest); // 3. 调用要测试的方法 ActivityService activityService = new ActivityService(); // 假设原类没有注入,直接调用 JSONArray result = activityService.getActivites(); // 4. 验证结果 assertEquals(2, result.length()); assertEquals(3, result.getJSONObject(0).getInt("id")); } } }
注意:静态模拟需要用try-with-resources来确保静态模拟的资源被正确释放,避免影响其他测试。
三、复用真实服务的数据
你提到的“先访问一次真实服务提取数据,后续测试复用”完全可行,步骤如下:
- 抓取真实数据:写一个简单的工具类或者main方法,调用一次真实的
getActivites(),把返回的JSONArray转成字符串,保存到项目的src/test/resources目录下(比如activities.json)。
// 示例代码,运行一次即可 public class DataFetcher { public static void main(String[] args) { ActivityService service = new ActivityService(); JSONArray activities = service.getActivites(); // 把activities.toString()写入到src/test/resources/activities.json // 可以用Files.write来实现 } }
- 测试时读取本地数据:在测试类中读取这个JSON文件,构造假的响应:
import org.json.JSONArray; import org.junit.jupiter.api.Test; import java.nio.file.Files; import java.nio.file.Paths; public class ActivityServiceTest { private JSONArray loadRealActivities() throws Exception { // 读取本地JSON文件 String jsonContent = new String(Files.readAllBytes(Paths.get("src/test/resources/activities.json"))); return new JSONArray(jsonContent); } @Test void testGetActivitiesWithRealData() throws Exception { JSONArray realActivities = loadRealActivities(); // 接下来用这个realActivities去构造假的JsonNode和HttpResponse,和前面的测试逻辑一样 // ... 省略模拟步骤,直接用realActivities替换fakeActivities即可 } }
这样每次测试都用真实的历史数据,不用再访问真实服务,同时保证数据的真实性。
最后提醒一下:原代码里jsonResponse可能为null(比如UnirestException发生时),直接调用jsonResponse.getBody()会抛出空指针异常,建议加上非空判断,像我在步骤2里修改的那样。
内容的提问来源于stack exchange,提问作者Bar Lerer
相关产品推荐
相关产品推荐

