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

使用Mockito模拟StateNotifierProvider(Riverpod)遇初始化断言错误

解决Riverpod StateNotifierProvider测试中未初始化的断言错误

你在测试按钮行组件时遇到断言错误:组件的按钮启用/禁用状态由控制器类的标志决定,手动测试正常,但自动化测试中,用Mockito创建MockUserSettingsController并覆盖StateNotifierProvider后,Widget的build方法执行ref.watch(userSettingsControllerProvider)时抛出以下错误:

Exception has occurred.
_AssertionError ('package:riverpod/src/framework/element.dart': Failed assertion: line 439 pos 9: 'getState() != null': Bad state, the provider did not initialize. Did "create" forget to set the state?)

你尝试过Stack Overflow上的两种方案,但因StateNotifier的state属性使用方式变更,且需要重写控制器的hasValueChanged()方法,这些方案无法适用。


相关代码

待测试Widget代码

class UserSettingsButtonRow extends ConsumerWidget {
  const UserSettingsButtonRow({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    ref.watch(userSettingsControllerProvider);
    UserSettingsController controller =
        ref.read(userSettingsControllerProvider.notifier);

...
}

测试代码

@GenerateNiceMocks([MockSpec<UserSettingsController>()])

void main() {
  testWidgets(
    'UserSettingsButtonRow - buttons enabled',
    (tester) async {
      // check if two buttons are enabled
      UserSettingsController settingsController = MockUserSettingsController();
      when(settingsController.hasValueChanged()).thenAnswer((_) => true);

      await tester.runAsync(
        () async {
          await tester.pumpWidget(
            ProviderScope(
              overrides: [
                userSettingsControllerProvider
                    .overrideWith((ref) => settingsController),
              ],
              child: const MaterialApp(
                home: Material(
                  child: UserSettingsButtonRow(),
                ),
              ),
            ),
          );
        },
      );

      await tester.pumpAndSettle();
      expect(find.byType(ElevatedButton), findsNWidgets(2));

      List<ElevatedButton> buttons = find
          .byType(ElevatedButton)
          .evaluate()
          .map((e) => e.widget as ElevatedButton)
          .toList();
      for (ElevatedButton button in buttons) {
        expect(button.enabled, isTrue);
      }
    },
  );

<.......>
}

解决方案

错误核心是Mock控制器未初始化StateNotifier的状态:当ref.watch调用时,Riverpod会尝试获取Provider的状态,但Mock对象的状态为null,触发了断言检查。

1. 给Mock控制器初始化状态

因为UserSettingsController继承自StateNotifier,必须确保它的state不为null。根据你的控制器状态类型,创建初始状态实例并通过Mockito模拟返回:

// 假设你的控制器状态类型是UserSettings,根据实际类定义初始化
final initialState = UserSettings(); 
UserSettingsController settingsController = MockUserSettingsController();
// 模拟state getter返回初始状态
when(settingsController.state).thenReturn(initialState);
when(settingsController.hasValueChanged()).thenAnswer((_) => true);

2. 调整Provider覆盖方式(可选)

如果使用Riverpod 2.x,也可以用overrideWithProvider创建临时Provider返回Mock,确保状态初始化逻辑被正确识别:

overrides: [
  userSettingsControllerProvider.overrideWithProvider(
    StateNotifierProvider((ref) => settingsController),
  ),
],

3. 验证Provider定义

检查你的userSettingsControllerProvider定义,确保状态类型不为空。如果业务允许状态为null,需显式声明可为空类型:

final userSettingsControllerProvider = StateNotifierProvider<UserSettingsController, UserSettings?>(...);

但更推荐确保状态始终有初始值,避免此类断言错误。


内容的提问来源于stack exchange,提问作者Vera Spöttl-Zeisberg

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 02:35:25