46

I have this class:

import 'package:flutter/material.dart';

class AgeText extends StatelessWidget {
  final String dateOfBirth;

  const AgeText({Key key, @required this.dateOfBirth}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final age = _calculateAge();
    return Text(age.toString());
  }

  int _calculateAge() {
    final dateOfBirthDate = DateTime.parse(dateOfBirth);
    final difference = DateTime.now().difference(dateOfBirthDate);
    final age = difference.inDays / 365;

    return age.floor();
  }
}

I'd like to test that it produces the correct age when a date of birth is passed into it. What is the best way to do this in Flutter?


SOLUTION: For those interested, here's the solution using @Günter Zöchbauer's suggestion of the clock package.

My widget class:

import 'package:flutter/material.dart';
import 'package:clock/clock.dart';

class AgeText extends StatelessWidget {
  final String dateOfBirth;
  final Clock clock;

  const AgeText({Key key, @required this.dateOfBirth, this.clock = const Clock()}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final age = _calculateAge();
    return Text(age.toString());
  }

  int _calculateAge() {
    final dateOfBirthDate = DateTime.parse(dateOfBirth);
    final difference = clock.now().difference(dateOfBirthDate);
    final age = difference.inDays / 365;

    return age.floor();
  }
}

and my test class:

import 'package:clock/clock.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app/age.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets("shows age 30 when date of birth is 30 years ago", (WidgetTester tester) async {
    final mockClock = Clock.fixed(DateTime(2000, 01, 01));
    final testableWidget = MaterialApp(
      home: AgeText(
        dateOfBirth: "1970-01-01T00:00:00",
        clock: mockClock,
      ),
    );

    await tester.pumpWidget(testableWidget);

    expect(find.text("30"), findsOneWidget);
  });
}
Jordan Davies
  • 9,925
  • 6
  • 40
  • 51

3 Answers3

24

As Günter said, the clock package, maintained by the Dart team, provides a very neat way to achieve this.

Normal usage:

import 'package:clock/clock.dart';

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

Overriding the current time:

import 'package:clock/clock.dart';

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

I wrote about this in more detail on my blog.

For widget tests, you need to wrap pumpWidget, pump and expect in the withClock callback.

Iiro Krankka
  • 4,959
  • 5
  • 38
  • 42
18

If you use the clock package for code depending on DateTime.now() you can easily mock it.

Other than creating a custom wrapper around DateTime.now(), I don't think there is a better way than what the clock package provides.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
11

As mentioned here: https://stackoverflow.com/a/63073876/2235274 implement extension on DateTime.

extension CustomizableDateTime on DateTime {
  static DateTime _customTime;
  static DateTime get current {
    return _customTime ?? DateTime.now();
  }

  static set customTime(DateTime customTime) {
    _customTime = customTime;
  }
}

Then just use CustomizableDateTime.current in the production code. You can modify the returned value in tests like that: CustomizableDateTime.customTime = DateTime.parse("1969-07-20 20:18:04");. There is no need to use third party libraries.

Adam Smaka
  • 5,977
  • 3
  • 50
  • 55
  • 2
    More concisely: `return customTime ?? DateTime.now();` – davejlin Dec 17 '20 at 22:17
  • 1
    This looks like a code smell. It is a bad practice to modify code for testing purposes. Also, customTime may result in bugs. You can just wrap DateTime.now() in a class with only a function that returns current. Then in the tests mock this class to return whatever you need to. – loshkin Jul 07 '22 at 18:56
  • 1
    @loshkin I agree. Also I prefer now to inject the DateTime to any function / class instead of getting it directly from inside. – Adam Smaka Jul 16 '22 at 07:21
  • 1
    @AdamSmaka An additional advantage if time injection is the ability in production code to have multiple things occur at precisely "the same moment," which is not possible if each thing gets its own slightly different time for "now." – Chuck Batson Dec 03 '22 at 17:19