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

如何对使用Retrofit发起网络请求的业务类进行单元测试

问题根因

你的模拟逻辑完全没有生效,核心是两点:

  1. 业务类XmattersApi的getGroupsWithSupervisor()方法内部硬编码创建了真实的Retrofit实例和XmattersInterface代理对象,测试中构造的mock实例根本没有被注入到业务类中使用,业务代码运行时还是会发起真实的网络请求,所以抛出SocketTimeoutException。
  2. 测试代码中混合使用了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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 00:24:02