如何对使用Retrofit发起网络请求的业务类进行单元测试
问题根因
你的模拟逻辑完全没有生效,核心是两点:
- 业务类
XmattersApi的getGroupsWithSupervisor()方法内部硬编码创建了真实的Retrofit实例和XmattersInterface代理对象,测试中构造的mock实例根本没有被注入到业务类中使用,业务代码运行时还是会发起真实的网络请求,所以抛出SocketTimeoutException。 - 测试代码中混合使用了retrofit-mock和Mockito mock,逻辑混乱,你同时mock了两个
XmattersInterface实例,却没有把任何一个传递给业务类。
修改方案
1. 重构业务类,移除硬编码的Retrofit初始化逻辑
把Retrofit服务实例的创建逻辑从方法中抽离,通过构造函数注入XmattersInterface,方便测试时替换为mock实例:
public class XmattersApi { private final XmattersInterface service; // 生产环境用的构造函数,传入配置类,内部初始化真实service public XmattersApi(XmattersApiConfiguration config) { Interceptor interceptor = chain -> { Request newRequest = chain.request().newBuilder() .addHeader("Authorization", "TestToken") .build(); return chain.proceed(newRequest); }; OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(interceptor) .build(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(getBaseUri(config.getHost()).toUriString()) .addConverterFactory(JacksonConverterFactory.create()) .client(client) .build(); this.service = retrofit.create(XmattersInterface.class); } // 测试用的构造函数,直接注入mock的service实例 public XmattersApi(XmattersInterface mockService) { this.service = mockService; } public GroupsResponse getGroupsWithSupervisor() throws IOException { Call<GroupsResponse> retrofitCall = service.getGroupsWithSupervisor("supervisors"); Response<GroupsResponse> response = retrofitCall.execute(); if (!response.isSuccessful()) { throw new IOException(response.errorBody() != null ? response.errorBody().string(): "unknown error"); } return response.body(); } }
2. 简化测试代码,把mock实例注入业务类
两种可选实现方案,不需要混合使用retrofit-mock和Mockito:
方案1:使用retrofit-mock模拟
public class XmattersApiTest { XmattersApi xmattersApi; private final NetworkBehavior behavior = NetworkBehavior.create(); @BeforeEach public void setUp() { // 构造retrofit-mock的模拟服务 Retrofit retrofit = new Retrofit.Builder() .addConverterFactory(JacksonConverterFactory.create()) .baseUrl("http://example.com") .build(); MockRetrofit mockRetrofit = new MockRetrofit.Builder(retrofit) .networkBehavior(behavior) .build(); BehaviorDelegate<XmattersInterface> delegate = mockRetrofit.create(XmattersInterface.class); XmattersInterface mockService = new XmattersInterfaceMock(delegate); // 把模拟服务注入业务类 xmattersApi = new XmattersApi(mockService); } @Test void testGroupsWithSupervisorCase_1() throws IOException { // 直接调用业务方法,内部会使用注入的mock服务返回模拟数据 GroupsResponse response = xmattersApi.getGroupsWithSupervisor(); // 断言返回结果符合预期即可 assertNotNull(response); } }
方案2:直接使用Mockito模拟(更简单,不需要自定义Mock类)
@ExtendWith(MockitoExtension.class) public class XmattersApiTest { @Mock private XmattersInterface mockService; private XmattersApi xmattersApi; @BeforeEach void init() { xmattersApi = new XmattersApi(mockService); } @Test void testGroupsWithSupervisorCase_1() throws IOException { // 构造模拟响应 GroupsResponse mockResponseData = new GroupsResponse(); // 可自行填充模拟数据的字段值 Response<GroupsResponse> mockResponse = Response.success(mockResponseData); Call<GroupsResponse> mockCall = mock(Call.class); when(mockCall.execute()).thenReturn(mockResponse); when(mockService.getGroupsWithSupervisor("supervisors")).thenReturn(mockCall); // 调用业务方法 GroupsResponse result = xmattersApi.getGroupsWithSupervisor(); // 断言结果符合预期 assertEquals(mockResponseData, result); } }
其他注意点
- 你原来测试代码里
addCallAdapterFactory(JacksonConverterFactory.create())写法错误,JacksonConverterFactory是转换器工厂,应该调用addConverterFactory方法注入,CallAdapter是适配RxJava之类的特殊返回值用的。 - 原来测试里的
mockCall.execute少了方法括号,正确写法是mockCall.execute()。 - 业务类里Retrofit的builder方法是
baseUrl不是baseurl,注意大小写,否则会编译报错。
内容的提问来源于stack exchange,提问作者Elvis Shrestha
相关产品推荐
相关产品推荐

