Flutter Mockito测试SoldRepository时FirebaseFirestore空指针报错解决
问题背景
开发环境运行无异常,仅测试场景下出现报错,具体情况如下:
方案1
测试代码如下:
void main() async { SoldRepository repository; FirebaseFirestore _firestore; final sold = Sold( buyerDisplayName: UserDisplayNameSold("Saugat Thapa"), buyerEmail: EmailAddressBought("saugat26@gmail.com", true), buyerPhotoUrl: UserPhotoUrlSold( " https://s3.amazonaws.com/uifaces/faces/twitter/salvafc/128.jpg"), buyerUserId: UserIdSold("haSW0s5MxOOENWnc5T1cLinekj2"), id: UniqueId(), quotations: List3Sold(KtList.of( Quotation( id: UniqueId(), measuremntUnit: QuotationUnit("Kilogram"), quantity: QuotationQuantity(10), rate: QuotationRate(12), title: QuotationTitle("Onion"), index: QuotationIndex(8), isSelected: QuotationSelected(false)), )), sellerDisplayName: UserDisplayNameSold("riwat rai"), sellerEmail: EmailAddressSold("riwatj@gmail.com"), sellerPhotoUrl: UserPhotoUrlSold( " https://s3.amazonaws.com/uifaces/faces/twitter/salvafc/128.jpg"), sellerUserId: UserIdSold("DULkECyxWzfBosXXBbOMpfnij1"), total: SoldTotalHere(10)); // User sellerUserDetail; final auth = MockFirebaseAuth(); final googleSignIn = MockGoogleSignIn(); u.User sellerUserDetail; sellerUserDetail = u.User( email: "ku1438693@gmail.com", photoUrl: "https://lh6.googleusercontent.com/-5HXS8qkwKnw/AAAAAAAAAAI/AAAAAAAAAAA/AAKWJJMiRHRadk3nDhlaLa-sU0_fsPrHhQ/photo.jpg", id: UniqueId(), displayName: "Lila Kunwar"); setUp(() async { repository = SoldRepository(_firestore); }); test('creates bill in collection sold', () async { //arrange //act repository.createTest(sold, sellerUserDetail); // assert expect(result, right(sold)); }); }
调用 repository.createTest(sold, sellerUserDetail) 时报错,怀疑问题出自未初始化的 FirebaseFirestore _firestore; 变量。
关联的仓库接口定义:
abstract class ISoldRepository { Future<Either<SoldFailure, Unit>> createTest( Sold sold, User sellerUserDetail); }
仓库实现代码:
import 'dart:developer'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:injectable/injectable.dart'; import 'package:dartz/dartz.dart'; import 'package:kt_dart/kt.dart'; import 'package:logger/logger.dart'; import 'package:mobile_shamagri_bloc/domain/auth/i_auth_facade.dart'; import 'package:mobile_shamagri_bloc/domain/auth/user.dart'; import 'package:mobile_shamagri_bloc/domain/core/errors.dart'; import 'package:mobile_shamagri_bloc/domain/sold/i_sold_repository.dart'; import 'package:mobile_shamagri_bloc/domain/sold/sold_failure.dart'; import 'package:mobile_shamagri_bloc/domain/sold/sold.dart'; import 'package:mobile_shamagri_bloc/domain/sold_not_form/sold_not_form.dart'; import 'package:mobile_shamagri_bloc/domain/sold_not_form/sold_not_form_failure.dart'; import 'package:mobile_shamagri_bloc/infrastructure/core/firestore_helpers.dart'; import 'package:mobile_shamagri_bloc/infrastructure/sold_reso/sold_dtos.dart'; import 'package:mobile_shamagri_bloc/injection.dart'; import 'package:rxdart/rxdart.dart'; @LazySingleton(as: ISoldRepository) class SoldRepository implements ISoldRepository { final FirebaseFirestore _firestore; SoldRepository(this._firestore); String buyerUserID; String buyerDisplayName; String buyerPhotoUrl; Future<Either<SoldFailure, Unit>> createTest( Sold sold, User sellerUserDetail) async { try { final soldDto = SoldDto.fromDomain( sold, sellerUserDetail, sold.buyerUserId.getOrCrash(), sold.buyerDisplayName.getOrCrash(), sold.buyerPhotoUrl.getOrCrash()); await _firestore .collection("sold")// 此处触发报错 .doc(soldDto.soldId) .collection("invoice") .doc() .set(soldDto.toJson()); return right(unit); } on FirebaseException catch (e) { if (e.message.contains('PERMISSION_DENIED')) { return left(const SoldFailure.insufficientPermission()); } else { var logger = Logger(); logger.wtf("unexpected error " + e.toString()); return left(const SoldFailure.unexpected()); } } } }
报错日志:
creates bill in collection sold [E] NoSuchMethodError: The method 'collection' was called on null. Receiver: null Tried calling: collection("sold") dart:core Object.noSuchMethod package:mobile_shamagri_bloc/infrastructure/sold_reso/sold_repository.dart 228:12 SoldRepository.createTest test\features\sold\presentation\bloc\create.dart 85:21 main.<fn> test\features\sold\presentation\bloc\create.dart 83:43 main.<fn> ===== asynchronous gap =========================== dart:async _completeOnAsyncError package:mobile_shamagri_bloc/infrastructure/sold_reso/sold_repository.dart SoldRepository.createTest test\features\sold\presentation\bloc\create.dart 85:21 main.<fn> test\features\sold\presentation\bloc\create.dart 83:43 main.<fn> NoSuchMethodError: The method 'collection' was called on null. Receiver: null Tried calling: collection("sold") dart:core Object.noSuchMethod package:mobile_shamagri_bloc/infrastructure/sold_reso/sold_repository.dart 228:12 SoldRepository.createTest test\features\sold\presentation\bloc\create.dart 88:31 main.<fn> test\features\sold\presentation\bloc\create.dart 83:43 main.<fn> ===== asynchronous gap =========================== dart:async _completeOnAsyncError package:mobile_shamagri_bloc/infrastructure/sold_reso/sold_repository.dart SoldRepository.createTest test\features\sold\presentation\bloc\create.dart 88:31 main.<fn> test\features\sold\presentation\bloc\create.dart 83:43 main.<fn> Expected: Right<dynamic, Sold>:<Right(Sold(id: Value(Right(6f567c00-16a0-11ec-b349-256f5961cbea)), total: Value(Right(10)), buyerEmail: Value(Right(saugat26@gmail.com)), quotations: Value(Right([Quotation(id: Value(Right(6f56ca20-16a0-11ec-8c22-5db758776962)), title: Value(Right(Onion)), measuremntUnit: Value(Right(Kilogram)), rate: Value(Right(12)), quantity: Value(Right(10)), isSelected: Value(Right(false)), index: Value(Right(8)))])), sellerEmail: Value(Right(rrj@gmail.com)), sellerUserId: Value(Right(DULkEuiCyzfBosXXBbOMpfnij1)), buyerUserId: Value(Right(hanSW0s5MxOONWnc5T1cLinekj2)), sellerDisplayName: Value(Right(riwat rai)), buyerDisplayName: Value(Right(Saugat Thapa)), sellerPhotoUrl: Value(Right( https://s3.amazonaws.com/uifaces/faces/twitter/salvafc/128.jpg)), buyerPhotoUrl: Value(Right( https://s3.amazonaws.com/uifaces/faces/twitter/salvafc/128.jpg))))> Actual: <Instance of 'Future<Either<SoldFailure, Unit>>'> package:test_api expect package:flutter_test/src/widget_tester.dart 428:3 expect test\features\sold\presentation\bloc\create.dart 90:5 main.<fn> test\features\sold\presentation\bloc\create.dart 83:43 main.<fn>
方案2
修改后的测试代码:
void main() async { SoldRepository repository; FirebaseFirestore _firestore; final sold = Sold( buyerDisplayName: UserDisplayNameSold("Saugat Thapa"), buyerEmail: EmailAddressBought("saugat26@gmail.com", true), buyerPhotoUrl: UserPhotoUrlSold( " https://s3.amazonaws.com/uifaces/faces/twitter/salvafc/128.jpg"), buyerUserId: UserIdSold("hanSW0s5MxOOENWnc5T1cLinekj2"), id: UniqueId(), quotations: List3Sold(KtList.of( Quotation( id: UniqueId(), measuremntUnit: QuotationUnit("Kilogram"), quantity: QuotationQuantity(10), rate: QuotationRate(12), title: QuotationTitle("Onion"), index: QuotationIndex(8), isSelected: QuotationSelected(false)), )), sellerDisplayName: UserDisplayNameSold("riwat rai"), sellerEmail: EmailAddressSold("riwatj@gmail.com"), sellerPhotoUrl: UserPhotoUrlSold( " https://s3.amazonaws.com/uifaces/faces/twitter/salvafc/128.jpg"), sellerUserId: UserIdSold("DULkEuiCyxWzfBosXXBbOMpfnij1"), total: SoldTotalHere(10)); // User sellerUserDetail; final auth = MockFirebaseAuth(); final googleSignIn = MockGoogleSignIn(); u.User sellerUserDetail; sellerUserDetail = u.User( email: "ku143863@gmail.com", photoUrl: "https://lh6.googleusercontent.com/-5HXS8qkwKnw/AAAAAAAAAAI/AAAAAAAAAAA/AAKWJJMiRHRadk3nDhlaLa-sU0_fsPrHhQ/photo.jpg", id: UniqueId(), displayName: "Lila Kunwar"); setUp(() async { repository = SoldRepository(_firestore); }); test('creates bill in collection sold', () async { //arrange //act ISoldRepository repository; final globalResult = repository.createTest(sold, sellerUserDetail); expect(globalResult, d.right(sold)); }); }
报错日志:
00:02 +0 -1: creates bill in collection sold [E] NoSuchMethodError: The method 'createTest' was called on null. Receiver: null Tried calling: createTest(Instance of '_$_Sold', Instance of '_$_User') dart:core Object.noSuchMethod test\features\sold\presentation\bloc\create.dart 88:37 main.<fn> test\features\sold\presentation\bloc\create.dart 83:43 main.<fn>
问题原因与解决方法
核心问题
- 依赖未初始化:
- 方案1中
FirebaseFirestore _firestore仅声明未赋值,传入仓库后为空,调用collection方法触发空指针 - 方案2中测试用例内部重复声明了
ISoldRepository repository;未赋值,直接调用方法触发空指针
- 测试逻辑错误:
createTest是异步方法,未加await就直接断言,导致拿到的是Future实例而非实际返回结果;且预期值类型不匹配,接口返回的是Either<SoldFailure, Unit>,不是Either<SoldFailure, Sold>
解决步骤
- 引入模拟Firestore依赖,初始化模拟实例传入仓库
- 删除测试用例内重复声明的未初始化仓库变量
- 调用异步方法加
await,同时匹配正确的预期值
修正后的测试代码示例
import 'package:cloud_firestore_mocks/cloud_firestore_mocks.dart'; void main() async { SoldRepository repository; // 初始化模拟Firestore实例 final FirebaseFirestore _firestore = MockFirebaseFirestore(); final sold = Sold( buyerDisplayName: UserDisplayNameSold("Saugat Thapa"), buyerEmail: EmailAddressBought("saugat26@gmail.com", true), buyerPhotoUrl: UserPhotoUrlSold( " https://s3.amazonaws.com/uifaces/faces/twitter/salvafc/128.jpg"), buyerUserId: UserIdSold("haSW0s5MxOOENWnc5T1cLinekj2"), id: UniqueId(), quotations: List3Sold(KtList.of( Quotation( id: UniqueId(), measuremntUnit: QuotationUnit("Kilogram"), quantity: QuotationQuantity(10), rate: QuotationRate(12), title: QuotationTitle("Onion"), index: QuotationIndex(8), isSelected: QuotationSelected(false)), )), sellerDisplayName: UserDisplayNameSold("riwat rai"), sellerEmail: EmailAddressSold("riwatj@gmail.com"), sellerPhotoUrl: UserPhotoUrlSold( " https://s3.amazonaws.com/uifaces/faces/twitter/salvafc/128.jpg"), sellerUserId: UserIdSold("DULkECyxWzfBosXXBbOMpfnij1"), total: SoldTotalHere(10)); final auth = MockFirebaseAuth(); final googleSignIn = MockGoogleSignIn(); u.User sellerUserDetail; sellerUserDetail = u.User( email: "ku1438693@gmail.com", photoUrl: "https://lh6.googleusercontent.com/-5HXS8qkwKnw/AAAAAAAAAAI/AAAAAAAAAAA/AAKWJJMiRHRadk3nDhlaLa-sU0_fsPrHhQ/photo.jpg", id: UniqueId(), displayName: "Lila Kunwar"); setUp(() async { repository = SoldRepository(_firestore); }); test('creates bill in collection sold', () async { // 异步方法加await获取返回值 final result = await repository.createTest(sold, sellerUserDetail); // 匹配正确的返回类型 expect(result, right(unit)); }); }
内容的提问来源于stack exchange,提问作者Saugat Thapa
相关产品推荐
相关产品推荐

