如何编写Dio超时单元测试 解决Mockito模拟Dio报错问题
测试失败根因定位
对照测试输出的报错信息,两个核心问题导致测试未按预期运行:
- 模拟Dio抛错的方法配置完全错位
- 实际抛出的对象是闭包而非预期的DioError实例
从报错日志threw <Closure: (dynamic) => DioError>可以直接验证:你给thenThrow传的不是异常本身,而是一个返回DioError的匿名函数,Mockito会直接把这个匿名函数当异常抛出,根本无法命中数据源里on DioError catch(e)的捕获逻辑。
问题修正步骤
1. 修正Mockito的抛错配置
thenThrow方法的入参规则是直接传入需要抛出的异常实例,不需要包一层返回异常的函数。你之前错误地按照thenAnswer的写法给thenThrow传了闭包,这是核心错误来源。
同时注意:5.x版本以上的Dio构造DioError时必须传入关联的RequestOptions,否则会触发内部参数校验异常,影响测试结果。
错误写法:
when(mockDio.post(paths.login, data: tParams.toJson())).thenThrow( (_) async => DioError(type: DioErrorType.CONNECT_TIMEOUT));
修正后的写法:
// 构造符合Dio内部校验规则的CONNECT_TIMEOUT类型DioError final connectTimeoutError = DioError( type: DioErrorType.CONNECT_TIMEOUT, requestOptions: RequestOptions(path: paths.login), ); when(mockDio.post(paths.login, data: tParams.toJson())) .thenThrow(connectTimeoutError);
2. 调整异步测试的断言逻辑
因为被测的login方法是异步返回Future的,直接同步传递闭包的写法容易出现Future未完成的状态识别偏差,改成async模式的断言写法更稳妥。
修改后的完整可运行测试代码:
test( 'Should throw [ConnectTimeOutException] when reach timeout', () async { // arrange when(mockNetworkInfo.isConnected).thenAnswer((_) async => true); // 构造符合Dio规则的超时类DioError final connectTimeoutError = DioError( type: DioErrorType.CONNECT_TIMEOUT, requestOptions: RequestOptions(path: paths.login), ); when(mockDio.post(paths.login, data: tParams.toJson())) .thenThrow(connectTimeoutError); // act + assert 合并,直接对异步方法做异常捕获校验 expect( () async => loginDataSource.login(params: tParams), throwsA(isA<ConnectTimeOutException>()), ); }, );
常见踩坑提醒
- 严格区分Mockito的
thenAnswer和thenThrow用法:thenAnswer:传入一个函数,函数返回值是mock方法的正常返回结果,用于模拟异步/带返回值的方法逻辑thenThrow:直接传入要抛出的异常实体,用于模拟方法抛错的场景
- 模拟Dio的异常时,构造
DioError必须匹配当前Dio版本的必填参数要求,否则即使模拟逻辑正确,构造异常的过程本身就会触发非预期报错 - 参数匹配时要确保
when子句里的入参和实际数据源调用时的入参完全一致,否则mock规则不会命中,会走真实的Dio网络请求逻辑
内容的提问来源于stack exchange,提问作者Tad
相关产品推荐
相关产品推荐

