Fake timer in flutter tests

Viewed 417

I have this method in dataprovider:

  Future<String> getAccessToken(
    String code,
    DateTime created,
    Duration expiresIn,
    Duration interval,
  ) async {
    final uri = Uri.https(_host, '/oauth2/device', <String, String>{
      'grant_type': 'device_token',
      'client_id': _clientId,
      'client_secret': _clientSecret,
      'code': code,
    });
    final headers = <String, String>{'Content-Type': 'application/json'};
    final response = await _client.post(uri, headers: headers);

    if (200 <= response.statusCode && response.statusCode < 400) {
      return response.body;
    }

    final error = jsonDecode(response.body);

    final isExpired = DateTime.now().isAfter(created.add(expiresIn));
    if (isExpired || error['error'] == 'code_expired') {
      throw GetAccessTokenPollingException.fromJson(response.body);
    }

    if (error['error'] == 'authorization_pending') {
      return Future.delayed(
        interval,
        () => getAccessToken(code, created, expiresIn, interval),
      );
    }

    throw GetAccessTokenException.fromJson(response.body);
  }

And I want to check that with error['error'] = 'authorization_pending', duration = 60 seconds and interval = 5 seconds _client.post is called 10 times before throwing GetAccessTokenPollingException but I have no idea how to do this.

_client is mocked Client from http package and here is unfinished test:

  test('getAccessToken polling', () async {
    final client = MockClient();
    when(client.post(any, headers: anyNamed('headers'))).thenAnswer(
      (_) async => Response('{"error": "authorization_pending"}', 400),
    );

    final authDataProvider = AuthenticationDataprovider(
      'clientId',
      'clientSecret',
      'host',
      client,
    );

    // expect(); what to do here?
    // - wait 60 seconds (is there any fake timer in dart tests?)
    // - check that _client.post is called 10 times
    // - check that next _client.post results with GetAccessTokenPollingException
  });
1 Answers

Finally found a solution.

test('getAccessToken polling', () async {
  final client = MockClient();
  when(client.post(any, headers: anyNamed('headers'))).thenAnswer(
    (_) async => Response('{"error": "authorization_pending"}', 400),
  );

  final authDataProvider = AuthenticationDataprovider(
    'clientId',
    'clientSecret',
    'host',
    client,
  );

  // - use small time for duration and interval to pass test faster
  // - if it's not an option, fake_async package can be used
  // - fake_async can't control the time reported by DateTime.now()
  //   since it's not part of dart:async.
  //   if DateTime.now() is used in test or testing method, replace it with clock.now()
  //   from clock package
  Future<String> getAccessToken() async => authDataProvider.getAccessToken(
        'code',
        DateTime.now(),
        const Duration(milliseconds: 50),
        const Duration(milliseconds: 20),
      );

  void verifyClientCalls() => verify(
        client.post(any, headers: anyNamed('headers')),
      ).called(3);

  // try/catch results with an exception in tests
  // should use except/throwsA to wrap an exception
  expect(
    () async => getAccessToken().whenComplete(verifyClientCalls),
    throwsA(isA<GetAccessTokenPollingException>()),
  );
});
Related