Flutter – Mockito – Using async in testing produces an error but using async* made it work fine?

Issue

In short, I’m following the ResoCoder TDD Course. The course is a bit outdated but I’m pretty sure it’s fine to follow along and make adjustments along the way. The course and the code in GitHub for this course is not null-safety.

And I’m not following the course exactly. I made a few changes here and there.

I’m just into the course at about episode 2 or 3 for context.

auth_usecases_test.dart

import 'package:dartz/dartz.dart';
import 'package:mockito/mockito.dart';
import 'package:storayge/core/auth/domain/entities/local_user.dart';
import 'package:storayge/core/auth/domain/repository/auth_repository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:storayge/core/auth/domain/usecases/auth_usecases.dart';

class MockAuthRepository extends Mock implements AuthRepository {}

void main() {
  late GetLocalUserDataFromRemote usecase;
  late MockAuthRepository mockAuthRepository;

  late String tUid;
  late LocalUser tLocalUser;

  setUp(() {
    mockAuthRepository = MockAuthRepository();
    usecase = GetLocalUserDataFromRemote(repository: mockAuthRepository);
    tLocalUser = const LocalUser(
      username: 'testUsername',
      email: 'testEmail@Email',
      uid: 'testUid',
    );
    tUid = 'testUid';
  });

  test(
    'should get LocalUser data from the repository',
    () async* {
      // arrange
      when(mockAuthRepository.getLocalUserDataFromRemote(uid: tUid))
          .thenAnswer((_) async => Right(tLocalUser));
      // act
      final result = await usecase(Params(uid: tUid));
      // assert
      expect(result, equals(Right(tLocalUser)));
      verify(mockAuthRepository.getLocalUserDataFromRemote(uid: tUid));
      verifyNoMoreInteractions(mockAuthRepository);
    },
  );
}

This is the "working" code. If I change the async* keyword to just async it produces this error:

type 'Null' is not a subtype of type 'Future<Either<Failure, LocalUser>>'

auth_usecases.dart

class GetLocalUserDataFromRemote implements Usecase<LocalUser, Params> {
  final AuthRepository repository;

  GetLocalUserDataFromRemote({required this.repository});

  @override
  Future<Either<Failure, LocalUser>> call(Params params) async {
    return repository.getLocalUserDataFromRemote(uid: params.uid);
  }
}

class Params extends Equatable {
  final String uid;

  Params({required this.uid});

  @override
  List<Object?> get props => [uid];
}

auth_repository.dart

import 'package:dartz/dartz.dart';

import '../../../errors/failures.dart';
import '../entities/local_user.dart';

abstract class AuthRepository {
  Future<Either<Failure, LocalUser>> getLocalUserDataFromRemote({
    required String uid,
  });

  Future<Either<Failure, LocalUser>> signInWithEmailAndPassword({
    required String email,
    required String password,
  });
}

local_user.dart

class LocalUser extends Equatable {
  final String username;
  final String email;
  final String uid;

  const LocalUser({
    required this.username,
    required this.email,
    required this.uid,
  });

  @override
  List<Object?> get props => [username, email, uid];
}

failures.dart

abstract class Failure extends Equatable {
  final List<Object> properties;

  const Failure({required this.properties});

  @override
  List<Object> get props => properties;
}

Why and how does changing the async tag make it work? I’m stumped.

Edit: My packages are all currently latest with sound null safety. Except dartz which im using 0.1.0-nullsafety.1

Edit 2: Some updates. First of all it seems like using the async* tag makes the test pass, BUT it does not seem to actually work. I mean the testing passes even when it should have not. I think I have found the solution. And its of course my fault. The mockito docs says that the mockito does support null safety, BUT with some code generation. With the custom mock classes and whatnot. I didnt do any, which is dumb and I hate myself. So ima do some of that and update it later on.

Solution

Okay. I finally found the answer. And yes, it’s my own dumb fault.
Basically mockito 5.0.0 does support null safety now, with some caveats. You will need to generate mock classes using some code generation. Which I did not. basically what I did is

class MockAuthRepository extends Mock implements AuthRepository {} // remove

@GenerateMocks([AuthRepository]) //new
void main() {
  late GetLocalUserDataFromRemote usecase;
  late MockAuthRepository mockAuthRepository;

  late String tUid;
  late LocalUser tLocalUser;

  setUp(() {
    mockAuthRepository = MockAuthRepository();
    usecase = GetLocalUserDataFromRemote(repository: mockAuthRepository);
    tLocalUser = const LocalUser(
      username: 'testUsername',
      email: 'testEmail@Email',
      uid: 'testUid',
    );
    tUid = 'testUid';
  });

  test(
    'should get LocalUser data from the repository',
    () async {
      // arrange
      when(mockAuthRepository.getLocalUserDataFromRemote(anyNamed('uid')))
          .thenAnswer((_) async => Right(tLocalUser));
      // act
      final result = await usecase(Params(uid: tUid));
      // assert
      expect(result, equals(Right(tLocalUser)));
      verify(mockAuthRepository.getLocalUserDataFromRemote(uid: tUid));
      verifyNoMoreInteractions(mockAuthRepository);
    },
  );
}

And generate it with build runner. It’s pretty easy. I’m just that dumb and didnt read the docs carefully.

Answered By – Nazrin Harris

Answer Checked By – Clifford M. (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *