-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Code that uses DateTime.now is untestable #28985
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
To be precise, 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 I recommend writing the code that actually uses
Then your test can import the private library (from src/ somewhere) and test it using a custom DateTime-returning function of your choice. 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 Obviously this approach would have its limitations:
|
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
use getter in production code. |
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) |
The problem extends to |
Here's a mixin that import 'package:flutter/foundation.dart';
/// A getter for [DateTime.now] that can be stubbed for testing.
mixin StubbableNow on ChangeNotifier {
/// The current date.
DateTime get now => _stubNow ?? DateTime.now();
DateTime _stubNow;
/// Set a stub value for [now], used for testing.
set stubNow(DateTime date) {
_stubNow = date;
notifyListeners();
}
} Example test void main() {
group('HoursStore', () {
test('Correctly determines if it is legal hours', () {
final hours = HoursStore(store);
final saturdayDay = DateTime(2020, 9, 5, 10, 10);
final saturdayNight = DateTime(2020, 9, 5, 20, 10);
/// Saturday day
hours.stubNow = saturdayDay;
expect(hours.isLegalHuntingHours, isTrue);
/// Saturday night
hours.stubNow = saturdayNight;
expect(hours.isLegalHuntingHours, isFalse);
});
});
} |
@Hixie any final comments? |
Final comments? |
Yes. Like should we close this issue. |
As far as I can tell the initial report is still valid. If someone writes a library that uses |
I apologize. If this is fixable someday like your initial description mentioned - great! |
I agree that this issue is not resolved, and that it probably should be at some point, even though there are alternatives available. |
Any update on this? 👀 |
Here's a use-case for this ticket. I implemented custom velocity tracking for pan and scale gestures. Velocity calculations depend upon changes in time. Flutter's gesture system doesn't include time information in the various I can use a However, I also need to track the time between gestures. Flutter reports a new scale gesture every time the user adds or removes a finger. I need to de-dup these gestures to avoid crazy velocities in the time it takes a user to remove two fingers from the screen. Using a If |
@matthew-carroll |
Any update on this? 👀 |
Going back to this: Couldn't we have something similar to IOOverrides or zones? We can test test('foo', () {
runZoned(() {
print('Hello world');
}, zoneValues: ZoneSpecification(
print: (self, parent, zone, line) {/* TODO my custom print logic */},
));
}) We can even override Couldn't we override test('foo', () {
runZoned(() {
DateTime.now();
}, zoneValues: ZoneSpecification(
now: (self, parent, zone) => 0,
));
}) |
I think we are allowing too many overrides in Can we make Someone will also want to control And they should probably agree, also with Timers. Or maybe the only thing you can override is a delta in microseconds since epoch. You don't change time zone, you don't change how time elapses (it still runs at speed 1s/s), all you can do is offset it when converted to To make a program run entirely reproducible, you need to intercept every intergration point with the operating system or runtime, anywhere where any information can be created at runtime, which wasn't in the program to begin with. (And that still assumes that your program isn't timing-dependent, but opting out of using "real time" is a good way to not notice timing dependencies. All your tests become deterministic, even if real life isn't.) If you use |
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 theZone
to drive it; this would letFakeAsync
drive it in a manner consistent with the rest of theFakeAsync
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).
The text was updated successfully, but these errors were encountered: