如何正确测试Flutter Riverpod中的AutoDisposeAsyncNotifier?
Riverpod AutoDisposeAsyncNotifier 本地化逻辑单元测试示例
一、先调整代码以支持测试
原代码中直接实例化SharedPreferencesService会导致无法Mock外部依赖,需通过依赖注入改造:
方案:通过Provider注入服务
// 定义SharedPreferencesService的Provider final sharedPreferencesServiceProvider = Provider<SharedPreferencesService>( (ref) => SharedPreferencesService(), ); class LocalizationNotifier extends AutoDisposeAsyncNotifier<Locale> { late final SharedPreferencesService _prefsService; Locale currentLocale = const Locale('en'); @override FutureOr<Locale> build() { // 从ref中读取服务实例 _prefsService = ref.read(sharedPreferencesServiceProvider); return _prefsService.getLocale(); } void changeLocale(Locale newLocale) { switch(currentLocale.languageCode) { case 'en': newLocale = const Locale('bg'); break; case 'bg': newLocale = const Locale('en'); break; default: newLocale = const Locale('en'); break; } currentLocale = newLocale; // 使用注入的服务实例 _prefsService.setLocale(newLocale); ref.invalidateSelf(); } } final localizationProvider = AsyncNotifierProvider.autoDispose<LocalizationNotifier, Locale>( LocalizationNotifier.new, );
二、测试依赖准备
在pubspec.yaml的dev_dependencies中添加测试所需包:
dev_dependencies: flutter_test: sdk: flutter mockito: ^5.4.0 riverpod_test: ^0.3.0 build_runner: ^2.4.0
生成Mock类
使用mockito生成SharedPreferencesService的Mock实现:
- 创建测试目录下的mock文件(如
test/mocks/mock_shared_preferences_service.dart):
import 'package:mockito/mockito.dart'; import 'package:flutter/widgets.dart'; import '../../lib/shared_preferences_service.dart'; class MockSharedPreferencesService extends Mock implements SharedPreferencesService {}
- 运行命令生成mock代码:
dart run build_runner build
三、编写单元测试用例
import 'package:flutter_test/flutter_test.dart'; import 'package:riverpod/riverpod.dart'; import 'package:mockito/mockito.dart'; import 'package:flutter/widgets.dart'; import '../lib/localization_notifier.dart'; import '../lib/shared_preferences_service.dart'; import 'mocks/mock_shared_preferences_service.dart'; void main() { late MockSharedPreferencesService mockPrefsService; // 每个测试前初始化mock实例 setUp(() { mockPrefsService = MockSharedPreferencesService(); }); group('LocalizationNotifier 测试组', () { test('初始化时从SharedPreferences加载默认语言en', () { // 配置mock:调用getLocale返回en when(mockPrefsService.getLocale()).thenReturn(const Locale('en')); // 创建Provider容器,替换真实服务为mock final container = ProviderContainer( overrides: [ sharedPreferencesServiceProvider.overrideWithValue(mockPrefsService), ], ); // 测试结束后销毁容器 addTearDown(container.dispose); // 监听Provider状态变化 final listener = Listener<AsyncValue<Locale>>(); container.listen( localizationProvider, listener, fireImmediately: true, ); // 验证初始状态和方法调用 verify(listener(null, const AsyncData(Locale('en')))).called(1); verify(mockPrefsService.getLocale()).called(1); }); test('初始缓存为bg时,加载后返回bg', () { when(mockPrefsService.getLocale()).thenReturn(const Locale('bg')); final container = ProviderContainer( overrides: [ sharedPreferencesServiceProvider.overrideWithValue(mockPrefsService), ], ); addTearDown(container.dispose); final listener = Listener<AsyncValue<Locale>>(); container.listen( localizationProvider, listener, fireImmediately: true, ); verify(listener(null, const AsyncData(Locale('bg')))).called(1); verify(mockPrefsService.getLocale()).called(1); }); test('调用changeLocale从en切换到bg并持久化', () async { // 初始状态返回en when(mockPrefsService.getLocale()).thenReturn(const Locale('en')); // 模拟setLocale执行成功 when(mockPrefsService.setLocale(any)).thenAnswer((_) async => null); final container = ProviderContainer( overrides: [ sharedPreferencesServiceProvider.overrideWithValue(mockPrefsService), ], ); addTearDown(container.dispose); // 获取Notifier实例 final notifier = container.read(localizationProvider.notifier); // 调用切换方法 notifier.changeLocale(const Locale('en')); // 验证状态和方法调用 expect(notifier.currentLocale, const Locale('bg')); verify(mockPrefsService.setLocale(const Locale('bg'))).called(1); expect(container.read(localizationProvider).value, const Locale('bg')); }); test('调用changeLocale从bg切换回en并持久化', () async { when(mockPrefsService.getLocale()).thenReturn(const Locale('bg')); when(mockPrefsService.setLocale(any)).thenAnswer((_) async => null); final container = ProviderContainer( overrides: [ sharedPreferencesServiceProvider.overrideWithValue(mockPrefsService), ], ); addTearDown(container.dispose); final notifier = container.read(localizationProvider.notifier); notifier.changeLocale(const Locale('bg')); expect(notifier.currentLocale, const Locale('en')); verify(mockPrefsService.setLocale(const Locale('en'))).called(1); expect(container.read(localizationProvider).value, const Locale('en')); }); }); }
四、测试说明
- 使用
ProviderContainer创建测试环境,通过overrides替换真实服务为Mock实例,实现依赖隔离 - 用
mockito配置Mock的方法返回值,验证方法调用次数和参数 - 通过
container.listen监听Provider状态变化,验证状态更新逻辑 - 每个测试用例独立,通过
setUp初始化Mock,addTearDown清理资源
内容的提问来源于stack exchange,提问作者Atanas Nankinski
相关产品推荐
相关产品推荐

