In Dart, we can get the current date and time by calling DateTime.now().

That’s easy enough. But what’s not that easy is testing it. Since there’s no built-in way to override what it returns, any Dart code that directly depends on DateTime.now() is not testable.

However, there’s a very neat way to tackle this, and it’s not what you might think.

Why testing DateTime.now() is hard

Let’s imagine that we’re making an app that tells the current day of the week.

We have a class called Greeter that returns the appropriate greeting for each day.

greeter.dart
import 'package:intl/intl.dart';

final _format = DateFormat('EEEE');

class Greeter {
  String greet() {
    final now = DateTime.now();
    final dayOfWeek = _format.format(now);
    return 'Happy $dayOfWeek, you!';
  }
}

For example, if it’s Tuesday, Greeter().greet() returns “Happy Tuesday, you!”.

Writing some failing tests

We want to be sure nobody introduces bugs to the very important greeting functionality, so we write a test case:

greeter_test.dart
void main() {
  test('returns a proper greeting on a Tuesday', () {
    final greeting = Greeter().greet();

    // How on Earth do we make this expectation pass
    // if it's not Tuesday when we run this test?
    expect(
      greeting,
      'Happy Tuesday, you!',
    );
  });
}

The problem with this test is that it passes only once a week.

Unless our team agrees to only run tests on Tuesdays, that test case is not very useful. To make it pass every day of the week, we need to be able to control the current time somehow.

Making it testable - the usual way

We could refactor Greeter to something like this:

greeter.dart
DateTime _getSystemTime() => DateTime.now();

class Greeter {
  Greeter([this._getCurrentTime = _getSystemTime]);
  final DateTime Function() _getCurrentTime;

  String greet() {
    final now = _getCurrentTime();
    final dayOfWeek = _format.format(now);
    return 'Happy $dayOfWeek, you!';
  }
}

The class now has an optional constructor parameter: a function called _getCurrentTime().

By default, it returns the current time, but the behavior can be overridden. In our tests, we pass Greeter a function that returns a fixed DateTime instead.

greeter_test.dart
void main() {
  test('returns a proper greeting on a Tuesday', () {
    final greeting = Greeter(() => DateTime(2020, 09, 01)).greet();
    expect(greeting, 'Happy Tuesday, you!');
  });
}

With these changes, our tests now pass 7 days a week!

The end?

That approach works for the very simple use case we had, but it’s not a fun thing to do at scale.

We would need to do this dance with every single class or widget that needs the current datetime. This inevitably results in “prop drilling” when dealing with widgets and other nested classes. That’s not fun.

Well, what about InheritedWidget - or better yet, provider?

We could come up with a class called Clock that encapsulates the behavior:

DateTime _defaultTime() => DateTime.now();

class Clock {
  Clock([this.now = _defaultTime]);
  final DateTime Function() now;
}

We could then pass Clock down with an InheritedWidget or provider to make it available for a subtree.

This would avoid the problem of prop drilling and still keep the logic testable. However, we wouldn’t be able to access current time without a BuildContext. It would also complicate accessing the Clock in initState(), since context is not available there yet.

Also, if we have a lot of Dart classes depending on the current datetime that are not Flutter widgets, we’d still have to pass the Clock as a constructor argument over and over again.

There’s got to be a better way!

What about just making Clock a singleton?

That would work.

I did it years back myself with inKino. You can see the source code for Clock here, and here’s an example test case, although it’s a bit specific to Redux.

However, I can’t help but feel a bit off with this one.

It’s global behavior that can be overridden from anywhere. This could result in frustrating, hard to notice bugs.

The better way - controlling time with package:clock

Even though there’s no built-in way to mock DateTime.now(), there’s a good alternative that comes pretty close.

It’s a package called clock, and it’s maintained by the Dart team.

The package exposes a top-level variable called clock. To get the current time, we call clock.now():

import 'package:clock/clock.dart';

void main() {
  // prints current date and time
  print(clock.now());
}

In order to override the current time, we wrap any code that calls clock.now() with a function called withClock:

import 'package:clock/clock.dart';

void main() {
  withClock(
    Clock.fixed(DateTime(2000)),
    () {
      // always prints 2000-01-01 00:00:00.
      print(clock.now());
    },
  );
}

The withClock function takes in two arguments:

  1. a Clock - in our case, a clock with a fixed date set to 2000/01/01.
  2. a function, inside of which the clock getter always returns what we passed in as the first argument.

That’s pretty neat.

It also only requires minimal changes to existing code. And there’s no need to add yet another constructor parameter and pass stuff around.

How does it work?

Short answer: it leverages Zone-local values.

Here’s how the withClock function looks like:

class Clock {
  // ...
}

// An unique object to use as a key for the clock.
final _clockKey = Object();

T withClock<T>(Clock clock, T Function() callback) {
  return runZoned(
    callback,
    zoneValues: {
      // Override _clockKey with the provided clock.
      _clockKey: clock,
    },
  );
}

It runs the passed anonymous function in a new Zone, setting a Zone-local value for the _clockKey to contain the provided clock.

Any code inside callback that queries _clockKey from the current Zone will receive an overridden Clock.

You might see where this is going.

// Look up if the current Zone has a clock assigned, and
// return that. If not, return a fresh new instance of Clock.
Clock get clock => Zone.current[_clockKey] as Clock? ?? Clock();

The top-level clock getter checks for the same _clockKey in the current Zone.

If a Clock already exists in the current Zone (=we have wrapped that piece of code with the withClock function), it returns the provided Clock.

Otherwise, it returns a fresh instance of Clock that defaults to returning DateTime.now().

Fixing our tests

Now that we have this brand new trick up our sleeves, we’ll finally make our sample test case work properly.

Let’s implement this in our Greeter class. To make it work, we only need to make one tiny modification to greeter.dart:

greeter.dart
import 'package:clock/clock.dart';

class Greeter {
  String greet() {
    // ...
    final now = clock.now();
    // ...
  }
}

After that, we wrap our tests with the withClock() function:

greeter_test.dart
void main() {
  test('returns a proper greeting on a Tuesday', () {
    final greeting = withClock(
      Clock.fixed(DateTime(2020, 09, 01)),
      () => Greeter().greet(),
    );

    expect(greeting, 'Happy Tuesday, you!');
  });
}

Now the test passes on every day of the week - not just Tuesday!

Other use cases

Aside from overriding current time in just unit tests, depending on your app, you might benefit from this in your manual testing too.

For example, in Reflectly, we have some things that should be only loaded once a day. In order to test this manually, I just wrap runApp with withClock that starts with the current date.

Then I bump the day by one to simulate tomorrow, the day after, and so on.