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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 14:06:04