Unit tests with mocktail pass but 'Null' is not a subtype of type 'Future<>' runtime exception is thrown

Viewed 1668

There are similar issues asking about the same error (e.g. here), but their cause was due to improper mocking. In my case, I seem to have the method properly mocked, yet when I debug in Visual Studio Code with All Exceptions enabled, then I get the runtime exception:

_TypeError (type 'Null' is not a subtype of type 'Future<AuthenticationToken?>')

If I continue the test past the exception (or simply debug the tests with All Exceptions disabled or simply run them without debugging), all my tests pass ok.

dependencies:
  hive: ^2.0.4
  hive_flutter: ^1.1.0

dev_dependencies:
  mocktail: ^0.1.4
import 'package:hive/hive.dart';

class AuthenticationRepository {
  static const _currentTokenKey = 'key';
  AuthenticationToken? _inMemoryToken;
  Future<Box<AuthenticationToken?>> _tokenBox;
  ...
  Future<AuthenticationToken?> activeToken() async =>
      _inMemoryToken ?? (await _tokenBox).get(_currentTokenKey);
  ...
}

Sample test file:

import 'package:app/src/data/authentication/repository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';

class MockAuthenticationRepository extends Mock
    implements AuthenticationRepository {}

void main() {
  AuthenticationRepository authenticationRepository;
  SUT sut; // SUT depends on AuthenticationRepository

  setUp(() {
    authenticationRepository = MockAuthenticationRepository();

    when(() => authenticationRepository.activeToken())
      .thenAnswer((realInvocation) => Future.value(AuthenticationToken()));
    
    sut = SUT(authenticationRepository);
  });

  test('some test', () async {
    await sut.someMethod();
    verify(() => authenticationRepository.activeToken()).called(1);
  });
}

mocktail exception

Here is the stack trace:

MockAuthenticationRepository.activeToken (/Users/davilin/Documents/Projects/app/flutter/app/lib/src/data/authentication/repository.dart:296)
main.initMocks.<anonymous closure> (/Users/davilin/Documents/Projects/app/flutter/app/test/network/token_refresh_interceptor_test.dart:33)
when.<anonymous closure> (/Users/davilin/.pub-cache/hosted/pub.dartlang.org/mocktail-0.1.4/lib/src/mocktail.dart:211)
main.initMocks (/Users/davilin/Documents/Projects/app/flutter/app/test/network/token_refresh_interceptor_test.dart:33)
main.<anonymous closure>.<anonymous closure> (/Users/davilin/Documents/Projects/app/flutter/app/test/network/token_refresh_interceptor_test.dart:52)
Declarer._runSetUps.<anonymous closure> (/Users/davilin/.pub-cache/hosted/pub.dartlang.org/test_api-0.4.2/lib/src/backend/declarer.dart:329)
Future.forEach.<anonymous closure> (dart:async/future.dart:495)
Future.doWhile.<anonymous closure> (dart:async/future.dart:535)
StackZoneSpecification._registerUnaryCallback.<anonymous closure>.<anonymous closure> (/Users/davilin/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0/lib/src/stack_zone_specification.dart:126)
StackZoneSpecification._run (/Users/davilin/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0/lib/src/stack_zone_specification.dart:208)
StackZoneSpecification._registerUnaryCallback.<anonymous closure> (/Users/davilin/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0/lib/src/stack_zone_specification.dart:126)
_rootRunUnary (dart:async/zone.dart:1436)
_CustomZone.runUnary (dart:async/zone.dart:1335)
_CustomZone.runUnaryGuarded (dart:async/zone.dart:1244)
_CustomZone.bindUnaryCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1281)
Future.doWhile (dart:async/future.dart:551)
Future.forEach (dart:async/future.dart:493)
Declarer._runSetUps (/Users/davilin/.pub-cache/hosted/pub.dartlang.org/test_api-0.4.2/lib/src/backend/declarer.dart:329)
<asynchronous gap> (Unknown Source:0)
StackZoneSpecification._registerUnaryCallback.<anonymous closure> (/Users/davilin/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0/lib/src/stack_zone_specification.dart:0)
<asynchronous gap> (Unknown Source:0)

I just wanted to document this, in case someone can offer an explanation why this happens.

1 Answers

I followed up with the author of mocktail here, and this is his explanation:

This is working as expected. The issue is you have "All Exceptions" enabled instead of just "Uncaught Exceptions". Internally, mocktail handles catching the TypeErrors so you should just uncheck "All Exceptions" and you're good to go.

Hope that helps

Related