Search code examples
flutterdartmockito

Type mismatch error when using Mockito with Firestore mocks in Flutter tests


I'm encountering a type mismatch error when trying to use Mockito with Firestore mocks in my Flutter tests. Specifically, I'm getting the following error:

The argument type 'MockCollectionReference<Object?>' can't be assigned to the parameter type 'CollectionReference<Map<String, dynamic>>'.

I have a Flutter project using Firestore for data storage. I'm trying to mock Firestore and its related classes (CollectionReference, DocumentReference) using Mockito for unit testing. When setting up the mock interactions (when(...).thenReturn(...)), I'm encountering type mismatch errors related to generics. Setup Details:

I've generated mocks using @GenerateMocks for FirebaseFirestore, CollectionReference, DocumentReference, and other relevant classes. In my test setup (setUp method), I'm using these mocks to stub Firestore method calls (collection, doc).

Setup Details:

I've generated mocks using @GenerateMocks for FirebaseFirestore, CollectionReference, DocumentReference, and other relevant classes. In my test setup (setUp method), I'm using these mocks to stub Firestore method calls (collection, doc).

Code:

@GenerateMocks([
FirebaseAuth,
FirebaseStorage,
FirebaseMessaging,
FirebaseFirestore,
CollectionReference,
DocumentReference,
CheckPhoneAuthUserUseCase,
CheckAnonymousUserUsecase,
GetCurrentUidUseCase,
WriteCurrentTaskToLogUseCase,
])

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();
  setupFirebaseCoreMocks(); // Mocks initialization for Firebase

  late AdministrationRemoteDataSourceRepositoryImpl repository;
  late MockCheckPhoneAuthUserUseCase mockCheckPhoneAuthUserUseCase;
  late MockCheckAnonymousUserUsecase mockCheckAnonymousUserUseCase;
  late MockGetCurrentUidUseCase mockGetCurrentUidUseCase;
  late MockWriteCurrentTaskToLogUseCase mockWriteCurrentTaskToLogUseCase;
  late MockFirebaseFirestore mockFirebaseFirestore;
  late MockCollectionReference mockCollectionReference;
  late MockDocumentReference mockDocumentReference;
  late MockFirebaseMessaging mockFirebaseMessaging;
  late MockFirebaseStorage mockFirebaseStorage;
  late MockFirebaseAuth mockFirebaseAuth;

  setUpAll(() async {
    await Firebase.initializeApp(); // Initialize Firebase app
  });

  setUp(() {
    mockCheckPhoneAuthUserUseCase = MockCheckPhoneAuthUserUseCase();
    mockCheckAnonymousUserUseCase = MockCheckAnonymousUserUsecase();
    mockGetCurrentUidUseCase = MockGetCurrentUidUseCase();
    mockWriteCurrentTaskToLogUseCase = MockWriteCurrentTaskToLogUseCase();
    mockFirebaseFirestore = MockFirebaseFirestore();
    mockCollectionReference = MockCollectionReference();
    mockDocumentReference = MockDocumentReference();
    mockFirebaseAuth = MockFirebaseAuth();
    mockFirebaseStorage = MockFirebaseStorage();
    mockFirebaseMessaging = MockFirebaseMessaging();

    repository = AdministrationRemoteDataSourceRepositoryImpl(
      firebaseFirestore: mockFirebaseFirestore,
      firebaseMessaging: mockFirebaseMessaging,
      firebaseAuth: mockFirebaseAuth,
      firebaseStorage: mockFirebaseStorage,
      remoteService: null,
    );

    when(mockFirebaseFirestore.collection(any))
        .thenReturn(mockCollectionReference);
    when(mockCollectionReference.doc(any))
        .thenReturn(mockDocumentReference);
  });

  group('verifyStore', () {
    const storeId = 'testStoreId';
    const currentState = false;
    const uid = 'testUid';

    test('should return Success when all checks pass and store is verified',
        () async {
      // Arrange
      when(mockCheckPhoneAuthUserUseCase.call()).thenAnswer((_) async => true);
      when(mockCheckAnonymousUserUseCase.call()).thenAnswer((_) async => false);
      when(mockGetCurrentUidUseCase.call()).thenAnswer((_) async => uid);
      when(mockDocumentReference.update(any))
          .thenAnswer((_) async => Right(Success(message: "Done")));
      when(mockWriteCurrentTaskToLogUseCase.call(any))
          .thenAnswer((_) async => null);

      // Act
      final result = await repository.verifyStore(storeId, currentState);

      // Assert
      expect(result, Right(Success(message: "Successfully updated")));
      verify(mockCheckPhoneAuthUserUseCase.call());
      verify(mockCheckAnonymousUserUseCase.call());
      verify(mockGetCurrentUidUseCase.call());
      verify(mockDocumentReference.update(any));
      verify(mockWriteCurrentTaskToLogUseCase.call(any));
    });

    test('should return Failure when user is not authenticated with phone number',
        () async {
      // Arrange
      when(mockCheckPhoneAuthUserUseCase.call()).thenAnswer((_) async => false);

      // Act
      final result = await repository.verifyStore(storeId, currentState);

      // Assert
      expect(result, Left(Failure(message: "Invalid access")));
      verify(mockCheckPhoneAuthUserUseCase.call());
      verifyNever(mockCheckAnonymousUserUseCase.call());
      verifyNever(mockGetCurrentUidUseCase.call());
      verifyNever(mockDocumentReference.update(any));
    });

    test('should return Failure when user is anonymous', () async {
      // Arrange
      when(mockCheckPhoneAuthUserUseCase.call()).thenAnswer((_) async => true);
      when(mockCheckAnonymousUserUseCase.call()).thenAnswer((_) async => true);

      // Act
      final result = await repository.verifyStore(storeId, currentState);

      // Assert
      expect(result, Left(Failure(message: "Invalid access")));
      verify(mockCheckPhoneAuthUserUseCase.call());
      verify(mockCheckAnonymousUserUseCase.call());
      verifyNever(mockGetCurrentUidUseCase.call());
      verifyNever(mockDocumentReference.update(any));
    });

    test('should return Failure when there is a SocketException', () async {
      // Arrange
      when(mockCheckPhoneAuthUserUseCase.call()).thenAnswer((_) async => true);
      when(mockCheckAnonymousUserUseCase.call()).thenAnswer((_) async => false);
      when(mockGetCurrentUidUseCase.call()).thenAnswer((_) async => uid);
      when(mockDocumentReference.update(any)).thenThrow(SocketException('No Internet'));

      // Act
      final result = await repository.verifyStore(storeId, currentState);

      // Assert
      expect(result, Left(Failure(message: 'No Internet')));
      verify(mockCheckPhoneAuthUserUseCase.call());
      verify(mockCheckAnonymousUserUseCase.call());
      verify(mockGetCurrentUidUseCase.call());
      verifyNever(mockDocumentReference.update(any));
    });

    test('should return Failure when there is an unexpected exception', () async {
      // Arrange
      when(mockCheckPhoneAuthUserUseCase.call()).thenAnswer((_) async => true);
      when(mockCheckAnonymousUserUseCase.call()).thenAnswer((_) async => false);
      when(mockGetCurrentUidUseCase.call()).thenAnswer((_) async => uid);
      when(mockDocumentReference.update(any)).thenThrow(Exception('Unexpected error'));

      // Act
      final result = await repository.verifyStore(storeId, currentState);

      // Assert
      expect(result, Left(Failure(message: 'Exception: Unexpected error')));
      verify(mockCheckPhoneAuthUserUseCase.call());
      verify(mockCheckAnonymousUserUseCase.call());
      verify(mockGetCurrentUidUseCase.call());
      verifyNever(mockDocumentReference.update(any));
    });
  });
}

Solution

  • mockCollectionReference is of type MockCollectionReference, which, since it's a generic, is shorthand for MockCollectionReference<Object?>.

    Generic<BaseClass> is not substitutable for Generic<DerivedClass>, so MockCollectionReference<Object?> (which we can consider to be a CollectionReference<Object?>) is not substitutable for a CollectionReference<Map<String, dynamic>>.

    You instead should explicitly declare mockCollectionReference as a MockCollectionReference<Map<String, dynamic>>:

      late MockCollectionReference<Map<String, dynamic>> mockCollectionReference;