如何在不传入Dio对象的情况下对服务进行单元测试?
解决SDK包装器单元测试Mock Dio且不暴露依赖的问题
核心思路:依赖倒置 + 隐藏具体实现
通过抽象网络请求接口,将Dio的具体实现封装在包内部,对外只暴露抽象接口。用户无需接触Dio,测试时只需Mock抽象接口即可。
1. 定义抽象HttpClient接口
创建与Dio解耦的抽象类,包含SDK需要的HTTP方法:
abstract class HttpClient { Future<Response> get(String path, {Map<String, dynamic>? queryParameters}); // 根据业务需求添加post、put等其他HTTP方法 }
2. 实现基于Dio的具体客户端
在包内部实现抽象接口,用Dio处理实际请求,这个类仅包内可见:
class DioHttpClient implements HttpClient { final Dio _dio; DioHttpClient(this._dio); @override Future<Response> get(String path, {Map<String, dynamic>? queryParameters}) { return _dio.get(path, queryParameters: queryParameters); } }
3. 修改SampleService,依赖抽象接口
调整构造函数,对外隐藏Dio实现,同时提供测试专用入口:
import 'package:meta/meta.dart'; class SampleService { final String url; final String apiKey; final HttpClient _httpClient; // 对外公开的构造函数,用户无需关心Dio SampleService({required this.url, required this.apiKey}) : _httpClient = DioHttpClient(Dio(BaseOptions( baseUrl: url, contentType: 'application/json', headers: {'API-Key': apiKey}))); // 测试专用构造函数,仅包内和测试代码可见 @visibleForTesting SampleService.withHttpClient({ required this.url, required this.apiKey, required HttpClient httpClient, }) : _httpClient = httpClient; Future<Bitcoin> getTransactionById(String id) async { try { return await SampleClient(_httpClient).getTransaction(id); } catch (obj) { throw Exception().throwException(obj); } } }
4. 调整SampleClient依赖抽象接口
让SampleClient同样依赖HttpClient,而非直接依赖Dio:
class SampleClient { final HttpClient _httpClient; SampleClient(this._httpClient); Future<Bitcoin> getTransaction(String id) async { final response = await _httpClient.get('/transactions/$id'); return Bitcoin.fromJson(response.data); } }
5. 单元测试Mock抽象接口
测试时无需操作Dio,直接MockHttpClient即可:
import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; class MockHttpClient extends Mock implements HttpClient {} void main() { group('SampleService', () { late MockHttpClient mockHttpClient; late SampleService sampleService; setUp(() { mockHttpClient = MockHttpClient(); sampleService = SampleService.withHttpClient( url: 'https://api.example.com', apiKey: 'test_key', httpClient: mockHttpClient, ); }); test('getTransactionById returns Bitcoin on success', () async { // 模拟成功响应 when(mockHttpClient.get('/transactions/123')) .thenAnswer((_) async => Response( data: {'id': '123', 'amount': 1.5}, statusCode: 200, requestOptions: RequestOptions(path: '/transactions/123'), )); final result = await sampleService.getTransactionById('123'); expect(result.id, '123'); expect(result.amount, 1.5); verify(mockHttpClient.get('/transactions/123')).called(1); }); }); }
关键优势
- 用户无感知Dio依赖:用户使用SDK时只需传入必要配置,无需导入或声明Dio依赖
- 测试解耦:测试代码只需Mock抽象接口,完全不依赖Dio的具体实现
- 扩展性强:后续替换网络库时,只需新增
HttpClient实现类,无需修改对外API和测试代码
内容的提问来源于stack exchange,提问作者Alvin
相关产品推荐
相关产品推荐

