Riverpod测试:如何使用StateNotifierProvider模拟状态?
错误根因
你遇到的type 'Null' is not a subtype of type '() => void'报错,核心原因是Riverpod内部运行时会调用StateNotifier的addListener方法监听状态变化,你自定义的Mock类没有对该方法做存根(stub),调用后返回null,和方法要求的返回值类型不匹配导致报错。
解决方案
这里提供两种常用方案,可根据测试场景选择:
方案1:Mock Notifier(适合需要验证Notifier方法调用的场景)
修正后的测试代码如下:
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:riverpodlocalization/models/locale/locale_state.dart'; import 'package:riverpodlocalization/widgets/language_picker.dart'; // 自定义Mock类 class MockLocaleStateNotifier extends Mock implements LocaleStateNotifier {} void main() { late MockLocaleStateNotifier mockNotifier; // 定义测试用的固定状态 const testState = LocaleState(locale: Locale('zh', 'CN')); setUp(() { mockNotifier = MockLocaleStateNotifier(); // 存根Riverpod内部必须调用的监听方法 when(() => mockNotifier.addListener( any(), fireImmediately: any(named: 'fireImmediately') )).thenReturn(() {}); when(() => mockNotifier.removeListener(any())).thenReturn(null); // 存根状态返回值 when(() => mockNotifier.state).thenReturn(testState); // 若测试逻辑会调用setLocale,也需要存根该方法 when(() => mockNotifier.setLocale(any())).thenReturn(null); }); Widget testingWidget() { return ProviderScope( overrides: [ // 用overrideWith替代废弃的overrideWithValue,符合新版本Riverpod规范 localeStateProvider.overrideWith(() => mockNotifier), // 必须覆写测试中用到的其他Provider,否则会调用真实实现报错 supportedLocalesProvider.overrideWithValue([ const Locale('zh', 'CN'), const Locale('en', 'US') ]), ], child: const MaterialApp( home: Scaffold(body: LanguagePicker()), ), ); } testWidgets('当前语言为中文时下拉框正确显示对应选项', (WidgetTester tester) async { await tester.pumpWidget(testingWidget()); // 替换为你实际的本地化文本即可 expect(find.text('简体中文'), findsOneWidget); }); }
方案2:直接覆写状态(适合仅校验UI表现的场景,更简单)
如果你的测试不需要验证Notifier的方法是否被调用,只是要验证不同状态下的UI展示效果,完全可以跳过Mock Notifier的步骤,直接覆写Widget依赖的派生Provider即可:
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:riverpodlocalization/widgets/language_picker.dart'; void main() { testWidgets('当前语言为中文时下拉框正确显示中文选项', (WidgetTester tester) async { await tester.pumpWidget(ProviderScope( overrides: [ // 直接覆写Widget用到的localeProvider,不需要关心底层Notifier实现 localeProvider.overrideWithValue(const Locale('zh', 'CN')), supportedLocalesProvider.overrideWithValue([ const Locale('zh', 'CN'), const Locale('en', 'US') ]), ], child: const MaterialApp(home: Scaffold(body: LanguagePicker())), )); expect(find.text('简体中文'), findsOneWidget); }); testWidgets('当前语言为英文时下拉框正确显示英文选项', (WidgetTester tester) async { await tester.pumpWidget(ProviderScope( overrides: [ localeProvider.overrideWithValue(const Locale('en', 'US')), supportedLocalesProvider.overrideWithValue([ const Locale('zh', 'CN'), const Locale('en', 'US') ]), ], child: const MaterialApp(home: Scaffold(body: LanguagePicker())), )); expect(find.text('English'), findsOneWidget); }); }
注意事项
- 所有测试Widget中通过
ref.watch/ref.read调用的Provider,都需要在测试的ProviderScope中显式覆写,否则会调用项目中的真实实现,触发依赖(比如本地存储、网络请求)导致测试报错。 - 若使用Mock方案,所有测试逻辑中会调用的Notifier方法都需要提前做存根,否则会因为未实现方法抛出异常。
内容的提问来源于stack exchange,提问作者Matthew Rideout
相关产品推荐
相关产品推荐

