Flutter测试中使用Mockito模拟Firestore出现类型不匹配错误
Flutter测试中Mockito模拟Firestore的泛型类型不匹配问题
错误信息
The argument type 'MockCollectionReference<Object?>' can't be assigned to the parameter type 'CollectionReference<Map<String, dynamic>>'.
问题背景
项目使用Firestore存储数据,尝试用Mockito模拟FirebaseFirestore、CollectionReference、DocumentReference等类进行单元测试,在设置when(...).thenReturn(...)存根方法时,出现泛型类型不匹配的错误。
配置情况
- 已通过
@GenerateMocks注解为FirebaseAuth、FirebaseStorage、FirebaseMessaging、FirebaseFirestore、CollectionReference、DocumentReference等类生成Mock对象 - 在测试的
setUp方法中,使用这些Mock对象存根Firestore的collection、doc方法
测试代码示例
@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)); }); }); }
解决方法
1. 生成带指定泛型的Mock类
Mockito默认生成的MockCollectionReference和MockDocumentReference泛型为Object?,但实际代码中使用的是CollectionReference<Map<String, dynamic>>,需要在@GenerateMocks中通过customMocks指定泛型参数:
@GenerateMocks([ FirebaseAuth, FirebaseStorage, FirebaseMessaging, FirebaseFirestore, CheckPhoneAuthUserUseCase, CheckAnonymousUserUsecase, GetCurrentUidUseCase, WriteCurrentTaskToLogUseCase, ], customMocks: [ // 指定CollectionReference的泛型为Map<String, dynamic> MockSpec<CollectionReference<Map<String, dynamic>>>(), // 指定DocumentReference的泛型为Map<String, dynamic> MockSpec<DocumentReference<Map<String, dynamic>>>(), ])
2. 调整测试中的变量类型
将测试代码中的Mock变量类型改为带泛型的版本:
// 替换原来的非泛型Mock类型 late MockCollectionReference<Map<String, dynamic>> mockCollectionReference; late MockDocumentReference<Map<String, dynamic>> mockDocumentReference; // 在setUp中初始化 mockCollectionReference = MockCollectionReference<Map<String, dynamic>>(); mockDocumentReference = MockDocumentReference<Map<String, dynamic>>();
3. 验证存根类型匹配
此时when(mockFirebaseFirestore.collection(any)).thenReturn(mockCollectionReference)的返回类型会与实际代码中期望的CollectionReference<Map<String, dynamic>>完全匹配,类型错误即可解决。
注意:如果项目中使用
withConverter将Firestore文档转换为自定义实体类,需要将泛型参数替换为对应的实体类类型。
内容的提问来源于stack exchange,提问作者Corner
相关产品推荐
相关产品推荐

