Dart单元测试:如何Mock AppointmentService类中的_appointmentCollection类变量?
_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
Alternative: If You Don't Want to Refactor (Not Recommended)
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

