I'm currently writing golden tests for my app, that uses Riverpod and Golden Toolkit, and I'm facing a bug that I don't understand. When running a test (see below), I get the following error:
Pending timers:
Timer (duration: 0:00:00.000000, periodic: false), created:
#0 new FakeTimer._ (package:fake_async/fake_async.dart:308:62)
#1 FakeAsync._createTimer (package:fake_async/fake_async.dart:252:27)
#2 FakeAsync.run.<anonymous closure> (package:fake_async/fake_async.dart:185:19)
#7 _defaultVsync (package:riverpod/src/framework/scheduler.dart:9:3)
#8 _ProviderScheduler._scheduleTask (package:riverpod/src/framework/scheduler.dart:42:10)
#9 _ProviderScheduler.scheduleProviderDispose (package:riverpod/src/framework/scheduler.dart:75:5)
#10 AutoDisposeProviderElementMixin.mayNeedDispose (package:riverpod/src/framework/auto_dispose.dart:40:29)
#11 ProviderElementBase._onRemoveListener (package:riverpod/src/framework/element.dart:897:5)
#12 _ExternalProviderSubscription.close (package:riverpod/src/framework/provider_base.dart:167:22)
#13 ConsumerStatefulElement.unmount (package:flutter_riverpod/src/consumer.dart:577:21)
#14 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1953:13)
#15 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1951:7)
#16 ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:5138:14)
#17 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1949:13)
(...)
#354 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1951:7)
#355 ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:5138:14)
#356 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1949:13)
#357 ListIterable.forEach (dart:_internal/iterable.dart:39:13)
#358 _InactiveElements._unmountAll (package:flutter/src/widgets/framework.dart:1962:25)
#359 BuildOwner.lockState (package:flutter/src/widgets/framework.dart:2640:15)
#360 BuildOwner.finalizeTree (package:flutter/src/widgets/framework.dart:3050:7)
#361 AutomatedTestWidgetsFlutterBinding.drawFrame (package:flutter_test/src/binding.dart:1405:19)
#362 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:358:5)
#363 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1284:15)
#364 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1214:9)
#365 AutomatedTestWidgetsFlutterBinding.scheduleWarmUpFrame (package:flutter_test/src/binding.dart:1332:5)
#366 runApp (package:flutter/src/widgets/binding.dart:1083:7)
#367 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:993:7)
<asynchronous suspension>
(elided 4 frames from dart:async)
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following assertion was thrown running a test:
A Timer is still pending even after the widget tree was disposed.
'package:flutter_test/src/binding.dart':
Failed assertion: line 1498 pos 12: '!timersPending'
When the exception was thrown, this was the stack:
#2 AutomatedTestWidgetsFlutterBinding._verifyInvariants (package:flutter_test/src/binding.dart:1498:12)
#3 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:1003:7)
<asynchronous suspension>
(elided 2 frames from class _AssertionError)
Here is the simplified test class, that I run using the following command: flutter test --update-goldens --tags=test
:
@Tags(["test"])
import 'dart:async';
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();
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 getPage(int a)
{
return ProviderScope(
key: ValueKey("page$a"),
child: const IntroPage(),
);
}
testGoldens("Test ProviderScopes", (WidgetTester tester) async
{
const density = 2.0;
const sizeBaseDp = Size(2048 / density, 2732 / density);
for (int a = 0; a < 2; a++) // If I do only one loop, it works...
{
await generateScreenshot(
tester: tester,
widget: getPage(a),
pageName: "page$a",
sizeDp: sizeBaseDp,
density: density);
}
});
}
final setupIntroNotifierProvider = AutoDisposeAsyncNotifierProvider<SetupIntroNotifier, bool>(() => SetupIntroNotifier());
class SetupIntroNotifier extends AutoDisposeAsyncNotifier<bool>
{
@override
FutureOr<bool> build() => false;
}
class IntroPage extends ConsumerStatefulWidget
{
const IntroPage({super.key});
@override
ConsumerState<IntroPage> createState() => _IntroPageState();
}
class _IntroPageState extends ConsumerState<IntroPage>
{
@override
Widget build(BuildContext context)
{
ref.listen<AsyncValue<bool>>(setupIntroNotifierProvider, (previous, next) {});
return const Placeholder();
}
}
The purpose here is to generate multiple images of the same page, but in different conditions (that I didn't put here for clarity sake).
The test passes if, in my test, I call generateScreenshot()
only once. But if I call it even two times, it crashes (even though the images are generated as expected).
I also noticed that the test also passes if I remove the key for the ProviderScope
, but then it doesn't behave as expected (the overrides don't seem to work)
Did I miss something? How can I make my test pass with multiple loops, while keeping the key for each ProviderScope
?
Thanks for your help.