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

多Widget不同ProviderScope跑黄金测试时遇重写数量变更错误

问题:Riverpod + Golden Toolkit黄金测试中ProviderScope覆盖报错

运行黄金测试时出现以下断言错误:

The following assertion was thrown building ProviderScope(state: ProviderScopeState#61f2f):
Tried to change the number of overrides. This is not allowed – overrides cannot be removed/added,
they can only be updated.
'package:riverpod/src/framework/container.dart':
Failed assertion: line 358 pos 7: '_debugOverridesLength == overrides.length'

测试代码如下(简化版):

@Tags(["screenshots"])
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:golden_toolkit/golden_toolkit.dart';

void main()
{
  WidgetsFlutterBinding.ensureInitialized();

  final testProvider = Provider<bool?>((ref) => null);

  Future<void> generateScreenshot({
    required WidgetTester tester,
    required Widget widget,
    required String pageName,
    required Size sizeDp,
    required double density,
    CustomPump? customPump,
  }) async
  {
    await tester.pumpWidgetBuilder(widget);
    await multiScreenGolden(
      tester,
      pageName,
      customPump: customPump,
      devices: [
        Device(
          name: "final",
          size: sizeDp,
          textScale: 1,
          devicePixelRatio: density,
        ),
      ],
    );
  }

  Widget getPage1()
  {
    return ProviderScope(
      overrides: [
        testProvider.overrideWithValue(true),
      ],
      child: Container(color: Colors.green),
    );
  }

  Widget getPage2()
  {
    return ProviderScope(
      overrides: [
        testProvider.overrideWithValue(false),
      ],
      child: Container(color: Colors.amber),
    );
  }

  testGoldens("Test ProviderScopes", (WidgetTester tester) async
  {
    const density = 2.0;
    const sizeBaseDp = Size(2048 / density, 2732 / density);

    await generateScreenshot(
        tester: tester,
        widget: getPage1(),
        pageName: "page1",
        sizeDp: sizeBaseDp,
        density: density);

    await generateScreenshot(
        tester: tester,
        widget: getPage2(),
        pageName: "page2",
        sizeDp: sizeBaseDp,
        density: density);
  });
  
}

需求是为两个不同Widget生成截图,每个Widget都有自己的ProviderScope配置。当两个overrides内容相同时测试通过,内容不同则崩溃,且在其他项目中类似操作正常。


解决方案

原因分析

问题出在同一个测试用例中复用了Riverpod的根ProviderScope容器:

  • 第一次调用pumpWidgetBuilder时,Riverpod会创建根容器并记录初始overrides的长度和内容。
  • 第二次pump不同配置的ProviderScope时,即使overrides数量相同,Riverpod也会认为是修改了overrides的结构(而非更新已有覆盖),触发断言错误。
  • 若两个overrides内容完全一致,Riverpod会判定为无变化,因此不会报错。

可行解决方法

方法1:拆分测试用例

将两个截图的生成逻辑拆分为独立的testGoldens用例,每个用例拥有独立的Riverpod容器,避免状态复用:

testGoldens("Test Page1", (WidgetTester tester) async {
  const density = 2.0;
  const sizeBaseDp = Size(2048 / density, 2732 / density);

  await generateScreenshot(
      tester: tester,
      widget: getPage1(),
      pageName: "page1",
      sizeDp: sizeBaseDp,
      density: density);
});

testGoldens("Test Page2", (WidgetTester tester) async {
  const density = 2.0;
  const sizeBaseDp = Size(2048 / density, 2732 / density);

  await generateScreenshot(
      tester: tester,
      widget: getPage2(),
      pageName: "page2",
      sizeDp: sizeBaseDp,
      density: density);
});

方法2:重置测试环境(单测试用例内)

在两次截图之间重置测试环境,销毁之前的Widget树和Riverpod容器,确保每次pump都是全新的状态:
修改generateScreenshot函数,添加重置逻辑:

Future<void> generateScreenshot({
  required WidgetTester tester,
  required Widget widget,
  required String pageName,
  required Size sizeDp,
  required double density,
  CustomPump? customPump,
}) async
{
  // 重置测试环境,清除之前的Widget树和状态
  await tester.pumpWidget(const SizedBox.shrink());
  await tester.reset();

  await tester.pumpWidgetBuilder(widget);
  await multiScreenGolden(
    tester,
    pageName,
    customPump: customPump,
    devices: [
      Device(
        name: "final",
        size: sizeDp,
        textScale: 1,
        devicePixelRatio: density,
      ),
    ],
  );
}

方法3:复用同一覆盖实例并更新值

如果必须在同一个测试用例中执行,可以预先创建一个可变的覆盖实例,每次更新其值而非创建新的Override:

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  final testProvider = Provider<bool?>((ref) => null);
  // 初始化覆盖实例
  var testOverride = testProvider.overrideWithValue(true);

  Widget getPage1() {
    return ProviderScope(
      overrides: [testOverride],
      child: Container(color: Colors.green),
    );
  }

  Widget getPage2() {
    // 更新覆盖值而非创建新实例
    testOverride = testProvider.overrideWithValue(false);
    return ProviderScope(
      overrides: [testOverride],
      child: Container(color: Colors.amber),
    );
  }

  // 测试用例保持不变...
}

内容的提问来源于stack exchange,提问作者matteoh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 20:07:16