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

Dart单元测试:如何Mock AppointmentService类中的_appointmentCollection类变量?

How to Mock the _appointmentCollection Variable in AppointmentService

The core issue here is that your _appointmentCollection is a private, hard-coded dependency inside AppointmentService—this makes it impossible to directly replace with a mock for testing. The cleanest solution is to use dependency injection to make this dependency injectable, which will let you pass a mocked CollectionReference during tests. Here's a step-by-step guide:

Step 1: Refactor AppointmentService for Dependency Injection

First, modify your service class to accept a CollectionReference as a constructor parameter (with a default value to keep production code working as before):

import 'package:cloud_firestore/cloud_firestore.dart';

class AppointmentService {
  final CollectionReference _appointmentCollection;

  // Constructor: inject the collection reference, default to Firebase's instance
  AppointmentService({CollectionReference? appointmentCollection})
      : _appointmentCollection = appointmentCollection ?? FirebaseFirestore.instance.collection('appointments');

  Future<bool> scheduleAppointment(
    Appointment appointment,
    String useremail,
  ) async {
    Map<String, dynamic> appointmentInfo = appointment.toJson();
    try {
      await _appointmentCollection.doc(useremail).update({
        "appointments": FieldValue.arrayUnion([appointmentInfo]),
      });
      return true;
    } catch (e) {
      return false;
    }
  }
}

This refactor doesn't break any existing production code, but now you can pass a mock CollectionReference during tests.

Step 2: Generate Mocks for Firestore Classes

You'll need mocks for CollectionReference and DocumentReference (since your method calls doc() followed by update()). Use mockito's code generation to create these:

First, add the necessary imports and annotations to your test file:

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'package:your_app/appointment_service.dart'; // Replace with your actual path
import 'package:mockito/annotations.dart';

// Generate mocks for Firestore classes
@GenerateMocks([CollectionReference, DocumentReference])
void main() {
  // Tests will go here
}

Then run the build runner command again to generate the mocks:

flutter pub run build_runner build

Step 3: Write Your Test Cases

Now you can set up the mocks, define their behavior, and test your service's logic:

@GenerateMocks([CollectionReference, DocumentReference])
void main() {
  group("AppointmentService Tests", () {
    late AppointmentService appointmentService;
    late MockCollectionReference mockCollectionRef;
    late MockDocumentReference mockDocRef;

    // Run before each test to reset mocks
    setUp(() {
      mockCollectionRef = MockCollectionReference();
      mockDocRef = MockDocumentReference();

      // Mock the `doc()` call to return our mock DocumentReference
      when(mockCollectionRef.doc(any())).thenReturn(mockDocRef);

      // Initialize the service with our mock collection
      appointmentService = AppointmentService(appointmentCollection: mockCollectionRef);
    });

    test("scheduleAppointment returns true when update succeeds", () async {
      // Mock a successful update operation
      when(mockDocRef.update(any())).thenAnswer((_) async => Future.value());

      // Call the method under test
      final result = await appointmentService.scheduleAppointment(Appointment(), "test@user.com");

      // Verify the result and interactions
      expect(result, isTrue);
      verify(mockCollectionRef.doc("test@user.com")).called(1);
      verify(mockDocRef.update(argThat(containsPair("appointments", any)))).called(1);
    });

    test("scheduleAppointment returns false when update throws an error", () async {
      // Mock a failed update (throw an exception)
      when(mockDocRef.update(any())).thenThrow(Exception("Firestore update failed"));

      // Call the method under test
      final result = await appointmentService.scheduleAppointment(Appointment(), "test@user.com");

      // Verify the error handling works
      expect(result, isFalse);
    });
  });
}

Why This Works

By injecting the CollectionReference, you decouple your service from the actual Firestore implementation. This lets you:

  • Control exactly how the mock collection/document behaves (success, failure, etc.)
  • Verify that the correct methods are being called with the right arguments
  • Test your service's business logic without needing a real Firestore instance

If you absolutely can't refactor for dependency injection, you could make _appointmentCollection visible for testing using @visibleForTesting (from the meta package):

import 'package:meta/meta.dart';

class AppointmentService {
  @visibleForTesting
  late CollectionReference appointmentCollection;

  AppointmentService() {
    appointmentCollection = FirebaseFirestore.instance.collection('appointments');
  }

  // ... rest of the code (update to use `appointmentCollection` instead of `_appointmentCollection`)
}

Then in tests, you can directly replace appointmentCollection with a mock. However, this is less clean than dependency injection, as it exposes internal implementation details.

内容的提问来源于stack exchange,提问作者Satya Achanta

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 06:48:56