Sdk: Code that uses DateTime.now is untestable

Created on 8 Mar 2017  路  5Comments  路  Source: dart-lang/sdk

For testing purposes it would be useful if there was a way to drive the dart:core clock manually, so that DateTime.now returned predictable times. One solution would be to provide a hook in the Zone to drive it; this would let FakeAsync drive it in a manner consistent with the rest of the FakeAsync clocks.

Currently there is code in Flutter's framework that is untested and basically untestable because it depends on the value returned from DateTime.now (for example, the code that highlights "today" in the date picker).

area-library core-n library-async library-core type-enhancement

All 5 comments

To be precise, DateTime.now is perfectly testable, but code that depends on it is hard to do coverage testing for since you can't control the values going into that code. Changing the behavior of DateTime.now means that you would no longer be testing DateTime.now.

Making every system integration point user configurable (using zones or similar in-program methods) isn't really viable. It's a never ending story, especially when the dart:io library is considered. The logical conclusion would be to make every static method or constructor mockable - and I'm sure some people would like that, but I think it's already been done too much.
I'm also concerned about adding overhead to DateTime.now - it's not really a method that suffers latency well. And changing DateTime.now probably also means changing the behavior of every clock in the system (or risk being inconsistent with StopWatch and Timer, even though those might use different system calls to do their ticking). All in all, it's adding overhead to the most timing critical part of the system, just for testing.

I recommend writing the code that actually uses DateTime.now to abstract over the now function, then you can instantiate it with either DateTime.now or, for testing, your own function, instead of making it a static hard-coded dependency on the time provider. If it's not your code, that's obviously harder to change.
For example, have the actual code in a helper library:

public library:
  abstract class MyTimeBasedClass {
    factory MyTimeBaseClass() : GeneralTimeBaseClass(DateTime.now);
    // interface...
  }
private library:
  class GeneralTimeBaseClass implements MyTimeBasedClass {
    DateTime Function() _now;
    GeneralTimeBaseClass(DateTime this._now());
    // impl
  }

Then your test can import the private library (from src/ somewhere) and test it using a custom DateTime-returning function of your choice.
Or have a static field where you can store the function, or check a Zone variable for an override. Again, that only affects your code.

That is, write your code for testing (reduce static dependencies) instead of requiring the entire system to be configurable for something you only do during testing anyway.

Alternatively, we can investigate making mocking of static functionality easier in a test-setup.

For example, I find it more scalable if it was possible another isolate that uses the debug-protocol to intercept a constructor-call to new DateTime.now() and returns the mocked object. I'm not sure if this is already fully possible, but this approach would make it possible to intercept every static call.

Obviously this approach would have its limitations:

  • it wouldn't work with dart2js.
  • a full AOT compilation with tree-shaking might not have enough code anymore to create a mock object.

You can use the Clock class from quiver.time instead of DateTime to get a fakeable Clock: https://github.com/google/quiver-dart#quivertime

create extension like this
extension CustomizableDateTime on DateTime { static DateTime customTime; static DateTime get current { if (customTime != null) return customTime; else return DateTime.now(); } }

use getter in production code.
in tests set expected value like this:
CustomizableDateTime.customTime = DateTime.parse('2012-02-19 13:27:00');

One option is to do it with package:clock, which is maintained by the Dart team:

import 'package:clock/clock.dart';

void main() {
  // prints current date and time
  print(clock.now());
}
import 'package:clock/clock.dart';

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

(I also wrote about it on my blog)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sgrekhov picture sgrekhov  路  3Comments

Hixie picture Hixie  路  3Comments

brooth picture brooth  路  3Comments

matanlurey picture matanlurey  路  3Comments

gspencergoog picture gspencergoog  路  3Comments