多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
相关产品推荐
相关产品推荐

