Flutter Dio单元测试报错:'Null'不是'Interceptors'的子类型
Flutter Dio 单元测试报错:type 'Null' is not a subtype of type 'Interceptors'
问题背景
开发Flutter应用时,使用Dio处理网络请求,为封装在AppClient中的Dio post方法编写单元测试时遇到类型不匹配错误。
核心代码
app_client.dart
abstract class AppClient{ Future<Map<String, dynamic>> get(String path, {Map<String, String>? queryParameters, Map<String, String>? headers}); Future<Map<String, dynamic>> post(String path, dynamic data, {Map<String, String>? queryParameters, Map<String, String>? headers}); } class AppClientImpl implements AppClient{ late Dio dio; late Interceptors interceptors; AppClientImpl({required this.dio}){ //获取语言编码 String languageCode = StorageUtil.getSavedLanguage(); if(languageCode == "id-ID"){ languageCode = "id_ID"; }else{ languageCode = "en_US"; } BaseOptions baseOptions = BaseOptions( baseUrl: ServiceUrl.baseUrl, headers: { "Accept" : "application/json", "Content-Type" : "application/json", }, queryParameters: { "language" : languageCode, "channel" : "mobile" } ); dio.options = baseOptions; interceptors = Interceptors(); interceptors.add(LogInterceptor(request: true, requestBody: true, requestHeader: true, responseBody: true, responseHeader: true)); //测试时注释仍报错 dio.interceptors.addAll(interceptors); // 错误发生位置!!! } @override Future<Map<String, dynamic>> get(String path, {Map<String, String>? queryParameters, Map<String, String>? headers}) async { // return await dio.get(path, queryParameters: queryParameters); throw UnimplementedError(); } Future<Map<String, dynamic>> post(String path, dynamic data, {Map<String, String>? queryParameters, Map<String, String>? headers}) async{ Response response = await dio.post(path, queryParameters: queryParameters, data: Map<String, dynamic>.from(data)); if(response.statusCode == 200){ return jsonDecode(response.data); }else{ throw Exception(); } } }
测试类代码
class MockDio extends Mock implements Dio{} void main(){ late MockDio mockDio; late AppClientImpl appClient; setUp((){ mockDio = MockDio(); appClient = AppClientImpl(dio: mockDio); }); final tResponse = jsonDecode(fixture("token/token_success.json")); final tData = {}; group("post method", (){ test( "状态码200时应返回数据", ()async{ when( () => mockDio.post(any(), queryParameters: any(named: "queryParameters"), data: any(named: "data")) ).thenAnswer( (invocation) async => Response(requestOptions: RequestOptions(path: "/sample"), data: fixture("token/token_success.json"), statusCode: 200) ); final result = await appClient.post("/sample", tData); verify(() => mockDio.post(any(), queryParameters: any(named: "queryParameters"), data: any(named: "data"))).called(1); expect(result, tResponse); } ); }); }
报错信息
Testing started at 08.54 ... package:dio/src/dio.dart 46:20 MockDio.interceptors package:eazyconnect/data/network/app_client/app_client.dart 44:9 new AppClientImpl test/data/network/app_client/app_client_test.dart 23:17 main.<fn> type 'Null' is not a subtype of type 'Interceptors'
错误原因
通过Mock生成的MockDio实例,所有未指定返回值的属性默认都会返回null,其中就包括interceptors属性。而在AppClientImpl的构造函数中执行dio.interceptors.addAll(interceptors)时,dio.interceptors返回的是null,但addAll方法要求接收一个Interceptors类型的实例,因此触发了类型不匹配的错误。
解决方法
方案1:为MockDio的interceptors指定返回值
在测试的setUp方法中,给mockDio的interceptors属性设置一个真实的Interceptors实例:
setUp((){ mockDio = MockDio(); // 为mockDio的interceptors赋值,避免返回null when(() => mockDio.interceptors).thenReturn(Interceptors()); appClient = AppClientImpl(dio: mockDio); });
方案2:重构AppClientImpl的构造逻辑
将拦截器的初始化逻辑改为可配置,允许外部传入自定义拦截器,测试时可以传入空拦截器:
// 修改AppClientImpl构造函数 class AppClientImpl implements AppClient{ late Dio dio; late Interceptors interceptors; AppClientImpl({required this.dio, Interceptors? customInterceptors}){ // ... 原有baseOptions逻辑 // 使用传入的拦截器,无传入则创建新实例 interceptors = customInterceptors ?? Interceptors(); interceptors.add(LogInterceptor(request: true, requestBody: true, requestHeader: true, responseBody: true, responseHeader: true)); dio.interceptors.addAll(interceptors); } // ... 其他方法不变 } // 测试中使用 setUp((){ mockDio = MockDio(); when(() => mockDio.interceptors).thenReturn(Interceptors()); // 传入空拦截器,避免LogInterceptor影响测试 appClient = AppClientImpl(dio: mockDio, customInterceptors: Interceptors()); });
方案3:隔离静态依赖(额外优化)
测试中StorageUtil.getSavedLanguage()是静态方法,会导致测试依赖外部存储状态,建议封装为可注入的服务并Mock:
// 封装语言获取逻辑为可注入服务 abstract class LanguageService { String getSavedLanguage(); } class LanguageServiceImpl implements LanguageService { @override String getSavedLanguage() => StorageUtil.getSavedLanguage(); } // 修改AppClientImpl构造函数 AppClientImpl({required this.dio, required LanguageService languageService, Interceptors? customInterceptors}){ String languageCode = languageService.getSavedLanguage(); // ... 后续逻辑不变 } // 测试中Mock LanguageService class MockLanguageService extends Mock implements LanguageService{} // 在setUp中使用 setUp((){ mockDio = MockDio(); when(() => mockDio.interceptors).thenReturn(Interceptors()); final mockLanguageService = MockLanguageService(); when(() => mockLanguageService.getSavedLanguage()).thenReturn("en_US"); appClient = AppClientImpl(dio: mockDio, languageService: mockLanguageService); });
内容的提问来源于stack exchange,提问作者fajar ainul
相关产品推荐
相关产品推荐

