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

如何在不传入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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 20:48:20