You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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. 依赖未初始化:
  • 方案1中FirebaseFirestore _firestore仅声明未赋值,传入仓库后为空,调用collection方法触发空指针
  • 方案2中测试用例内部重复声明了ISoldRepository repository;未赋值,直接调用方法触发空指针
  1. 测试逻辑错误:createTest是异步方法,未加await就直接断言,导致拿到的是Future实例而非实际返回结果;且预期值类型不匹配,接口返回的是Either<SoldFailure, Unit>,不是Either<SoldFailure, Sold>

解决步骤

  1. 引入模拟Firestore依赖,初始化模拟实例传入仓库
  2. 删除测试用例内重复声明的未初始化仓库变量
  3. 调用异步方法加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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 21:06:03