Flutter Riverpod与Golden测试报错:组件树销毁后仍存在待处理Timer
解决Riverpod+Golden Toolkit循环测试的Timer pending问题
核心原因
循环调用带唯一key的ProviderScope时,部分Provider内部启动的Timer未在Widget树销毁时被正确取消,多次叠加后触发残留Timer报错。以下是保留ProviderScope key的前提下的解决方法:
1. 规范Provider内部Timer的生命周期管理
所有涉及Timer的Provider(如StateNotifier、ChangeNotifier)必须在dispose回调中主动取消Timer:
class CountNotifier extends StateNotifier<int> { Timer? _periodicTimer; CountNotifier() : super(0) { _periodicTimer = Timer.periodic(const Duration(seconds: 1), (_) { state++; }); } @override void dispose() { _periodicTimer?.cancel(); // 销毁时强制取消Timer super.dispose(); } }
确保所有异步定时器都被绑定到Provider的生命周期,避免残留。
2. 手动控制ProviderContainer的销毁时机
在测试循环中,手动创建ProviderContainer并绑定到ProviderScope,测试完成后主动销毁容器,强制清理Provider资源:
for (final testCase in testCases) { // 手动创建容器 final container = ProviderContainer( overrides: [/* 你的覆盖配置 */], ); await generateScreenshot( tester, widget: ProviderScope( key: Key(testCase.key), container: container, child: YourAppWidget(), ), name: testCase.name, ); // 测试后销毁容器,触发Provider dispose container.dispose(); // 等待所有异步清理操作完成 await tester.pumpAndSettle(); }
3. 用Golden Toolkit的scenarioWrapper封装清理逻辑
将ProviderScope的创建和销毁逻辑封装到场景包装器中,利用addTearDown确保每个测试场景结束后自动清理:
await multiScreenGolden( tester, 'multi-scenario-golden', scenarios: testCases.map((case) => Scenario( widget: YourAppWidget(), name: case.name, )).toList(), scenarioWrapper: (child) { final container = ProviderContainer(overrides: [/* 覆盖配置 */]); // 每个场景用唯一key final scope = ProviderScope( key: Key(UniqueKey().toString()), container: container, child: child, ); // 注册销毁回调 addTearDown(() => container.dispose()); return scope; }, );
4. 针对一次性Timer的临时处理
如果是短周期的一次性Timer,可在测试后等待Timer执行完成再销毁Widget树:
await generateScreenshot(...); // 等待Timer执行完毕 await tester.pump(const Duration(milliseconds: 500)); await tester.pumpAndSettle();
注意:此方法仅适用于一次性Timer,周期性Timer仍需通过Provider dispose取消。
内容的提问来源于stack exchange,提问作者matteoh
相关产品推荐
相关产品推荐

