Mocking time in UI tests in Flutter

When working with tests that test logic related to time, it is necessary to mock the time, especially if that time is being rendered in screenshot tests.

My code from the stone age would take an optional DateTime? now parameter to mock it. Then in the method, I would run now ?? DateTime.now() to determine the current time. But the cleaner way is to use Clock.

clock | Dart package
A fakeable wrapper for dart:core clock APIs.

So that's what I did. Before:

testWidgets('share text', (WidgetTester tester) async {
  initializeTimeZones();
  await initializeDateFormatting(AppLocale);
  await loadFont();

  ...
  await tester.pumpWidget(
    MyApp(now: todayAtNoon),
  );
  await tester.pumpAndSettle();

  await expectLater(
    find.byType(MaterialApp),
    matchesGoldenFile(
      'goldens/app/iosShareText1.png',
    ),
  );

  await tester.tap(find.byIcon(Icons.send).first);
  await tester.pumpAndSettle();
  // TODO: fix clock issues to show the message having been sent.
  await expectLater(
    find.byType(MaterialApp),
    matchesGoldenFile(
      'goldens/app/iosShareText2Sent.png',
    ),
  );
});

testWidgets('share image', (WidgetTester tester) async {
  ...
});

After:

withClock(Clock.fixed(todayAtNoon.add(Duration(hours: 3))), () async {
  testWidgets('share text', (WidgetTester tester) async {
    initializeTimeZones();
    await initializeDateFormatting(AppLocale);
    await loadFont();

    ...
    await tester.pumpWidget(
      MyApp(),
    );
    await tester.pumpAndSettle();

    await expectLater(
      find.byType(MaterialApp),
      matchesGoldenFile(
        'goldens/app/iosShareText1.png',
      ),
    );

    await tester.tap(find.byIcon(Icons.send).first);
    await tester.pumpAndSettle();
    // TODO: fix clock issues to show the message having been sent.
    await expectLater(
      find.byType(MaterialApp),
      matchesGoldenFile(
        'goldens/app/iosShareText2Sent.png',
      ),
    );
  });

  testWidgets('share image', (WidgetTester tester) async {
    ...
  });
});

I wrapped all of my tests in a withClock instead of using withClock in each testWidgets to not have to write the same withClock boilerplate. However, in my screenshot, the time was still not the mocked time. It was the actual current time!

So I added this code inside the withClock and inside the testWidgets to check the value of clock:

print(clock.now());

And indeed, as soon as in goes inside testWidgets, clock gets reset back.

So I had to invert how I set up time and add withClock inside each testWidgets:

testWidgets('share text', (WidgetTester tester) async {
  withClock(Clock.fixed(todayAtNoon.add(Duration(hours: 3))), () async {
    initializeTimeZones();
    await initializeDateFormatting(AppLocale);
    await loadFont();

    ...
    await tester.pumpWidget(
      MyApp(),
    );
    await tester.pumpAndSettle();

    await expectLater(
      find.byType(MaterialApp),
      matchesGoldenFile(
        'goldens/app/iosShareText1.png',
      ),
    );

    await tester.tap(find.byIcon(Icons.send).first);
    await tester.pumpAndSettle();
    // TODO: fix clock issues to show the message having been sent.
    await expectLater(
      find.byType(MaterialApp),
      matchesGoldenFile(
        'goldens/app/iosShareText2Sent.png',
      ),
    );
  });

  testWidgets('share image', (WidgetTester tester) async {
    withClock(Clock.fixed(todayAtNoon.add(Duration(hours: 3))), () async {
      ...
    });
  });
});

Now the screenshot shows up correctly with the mocked time, but I get this error:

══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following StateError was thrown running a test (but after the test had completed):
Bad state: No element

When the exception was thrown, this was the stack:
#0      Iterable.first (dart:core/iterable.dart:663:7)
#1      _FirstFinderMixin.filter (package:flutter_test/src/finders.dart:1332:28)
#3      Iterable.isEmpty (dart:core/iterable.dart:560:33)
#4      WidgetController._getElementPoint (package:flutter_test/src/controller.dart:2008:18)
#5      WidgetController.getCenter (package:flutter_test/src/controller.dart:1861:12)
#6      WidgetController.tap (package:flutter_test/src/controller.dart:1041:7)
#7      main.<anonymous closure>.<anonymous closure>.<anonymous closure> (file:///[project_path]/test/app_test.dart:216:22)
<asynchronous suspension>
(elided one frame from dart:async-patch)
════════════════════════════════════════════════════════════════════════════════════════════════════

How can it not find the Send icon button? I can see it fine in the screenshot.

Here's what Google AI Overview has to say about clock:

Really? clock is automatically mocked and set to Jan 1, 2015? So I try it:

void main() {
  testWidgets('signs in', (WidgetTester tester) async {
    print(clock.now());
    ...

Nope, it shows me the current time.

After some more research, I find this post: https://docs.flutter.dev/release/breaking-changes/test-widgets-flutter-binding-clock. It mentions tester.binding.clock. Let's try it:

void main() {
  testWidgets('signs in', (WidgetTester tester) async {
    print(clock.now());
    print(tester.binding.clock.now());

And it outputs:

00:01 +0: signs in
2025-09-16 09:55:26.754504
2015-01-01 00:00:00.000Z

WOW!

What if I start a zone with withClock that uses the same Clock? Then the test and the logic's Clock would match, and pump would advance the same time...

void main() {
  testWidgets('signs in', (WidgetTester tester) async {
    withClock(tester.binding.clock, () async {
      print(clock.now());
      print(tester.binding.clock.now());

      initializeTimeZones();
      await initializeDateFormatting(AppLocale);
      await loadFont();
      ...
      await tester.pumpWidget(
        MyApp(),
      );

      expect(find.text('Sign in'), findsOneWidget);

Nope, it still cannot find the button.

So I searched some more, and stumbled upon this blog post: https://retired.re-ynd.com/friends-roster-testing-in-flutter/.

He did the same: testWidgets inside of which he calls withClock, then renders his UI, and finds the element correctly. How come?

The only difference I can find is he awaits withClock. Let's try that.

void main() {
  testWidgets('signs in', (WidgetTester tester) async {
    await withClock(tester.binding.clock, () async {
      initializeTimeZones();
      await initializeDateFormatting(AppLocale);
      await loadFont();
      ...
      await tester.pumpWidget(
        MyApp(),
      );

      expect(find.text('Connexion'), findsOneWidget);
      expect(find.text('hello'), findsNothing);

      await expectLater(
        find.byType(MaterialApp),
        matchesGoldenFile(
          'goldens/app/signedOut.png',
        ),
      );
      ...

Mmh... It actually works!! Haha. Somehow I expected withClock to be synchronous, but it actually returns the result of the callback, which is a Future since I am using async.