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

如何正确测试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实现:

  1. 创建测试目录下的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 {}
  1. 运行命令生成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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 08:07:05