如何利用firestore-mock测试Angular的Firestore数据库服务方法?
Let's fix up your test setup and write proper test cases for your DatabaseService methods. First, we'll clean up the unnecessary parts in your spec file, then set up the mock correctly using firestore-mock to verify each method's behavior.
Step 1: Clean Up the Test File
First, remove imports that aren't relevant to testing DatabaseService (like LoginComponent, HttpClientTestingModule, etc.). We only need imports for Angular testing utilities, your service, and firestore-mock.
Step 2: Build a Proper AngularFirestore Mock
We'll create a mock AngularFirestore class that wraps the firestore-mock instance, so we can track method calls and validate interactions with Firestore.
Make sure you have @types/firebase installed for type support (if you don't already):
npm install --save-dev @types/firebase
Update your spec file's mock and TestBed configuration:
import { TestBed } from '@angular/core/testing'; import { DatabaseService } from './database.service'; import { AngularFirestore } from '@angular/fire/firestore'; import * as FirestoreMock from 'firestore-mock'; // Mock AngularFirestore to wrap firestore-mock functionality class MockAngularFirestore { private firestoreInstance = new FirestoreMock(); collection(subject: string) { const collectionRef = this.firestoreInstance.collection(subject); return { doc: (key: string) => { const docRef = collectionRef.doc(key); return { set: (data: any) => docRef.set(data), ref: { get: () => docRef.get() }, delete: () => docRef.delete() }; } }; } // Expose the firestore instance for direct data checks get firestore() { return this.firestoreInstance; } } describe('DatabaseService', () => { let service: DatabaseService; let mockFirestore: MockAngularFirestore; beforeEach(() => { TestBed.configureTestingModule({ providers: [ DatabaseService, { provide: AngularFirestore, useClass: MockAngularFirestore } ] }); service = TestBed.inject(DatabaseService); mockFirestore = TestBed.inject(AngularFirestore) as unknown as MockAngularFirestore; });
Step 3: Test pushToDatabase
We need to verify that the method calls Firestore's set() with the correctly serialized object.
describe('#pushToDatabase', () => { it('should serialize and save the object to the specified document', () => { // Arrange const testSubject = 'test-collection'; const testKey = 'test-doc-id'; const testObject = { name: 'Test Item', count: 42 }; const serializedObject = JSON.parse(JSON.stringify(testObject)); // Spy on Firestore methods to track calls const collectionSpy = spyOn(mockFirestore, 'collection').and.callThrough(); const docSpy = spyOn(mockFirestore.collection(testSubject), 'doc').and.callThrough(); const setSpy = spyOn(mockFirestore.collection(testSubject).doc(testKey), 'set'); // Act service.pushToDatabase(testSubject, testKey, testObject); // Assert expect(collectionSpy).toHaveBeenCalledWith(testSubject); expect(docSpy).toHaveBeenCalledWith(testKey); expect(setSpy).toHaveBeenCalledWith(serializedObject); }); });
Step 4: Test getData
For this async method, we'll pre-populate the mock Firestore with data, then verify the method returns the correct document data.
describe('#getData', () => { it('should fetch and return the document data', async () => { // Arrange const testSubject = 'test-collection'; const testKey = 'test-doc-id'; const testData = { name: 'Test Item', count: 42 }; // Pre-populate mock Firestore with test data await mockFirestore.firestore.collection(testSubject).doc(testKey).set(testData); // Act const result = await service.getData(testSubject, testKey); // Assert expect(result).toEqual(testData); // Verify the get method was called const getSpy = spyOn(mockFirestore.collection(testSubject).doc(testKey).ref, 'get').and.callThrough(); await service.getData(testSubject, testKey); expect(getSpy).toHaveBeenCalled(); }); });
Step 5: Test deleteData
We'll verify that the method deletes the specified document and that the document no longer exists in the mock Firestore.
describe('#deleteData', () => { it('should delete the specified document', async () => { // Arrange const testSubject = 'test-collection'; const testKey = 'test-doc-id'; const testData = { name: 'Test Item' }; // Add a document to delete await mockFirestore.firestore.collection(testSubject).doc(testKey).set(testData); let preDeleteDoc = await mockFirestore.firestore.collection(testSubject).doc(testKey).get(); expect(preDeleteDoc.exists).toBe(true); // Spy on the delete method const deleteSpy = spyOn(mockFirestore.collection(testSubject).doc(testKey), 'delete').and.callThrough(); // Act await service.deleteData(testSubject, testKey); // Assert expect(deleteSpy).toHaveBeenCalled(); const postDeleteDoc = await mockFirestore.firestore.collection(testSubject).doc(testKey).get(); expect(postDeleteDoc.exists).toBe(false); }); }); });
Key Tips
- Use
callThrough()on spies to ensure the mock's actual implementation runs (so we can validate side effects like data being added or deleted). - For async methods, always use
async/awaitin test cases to handle promises correctly and avoid race conditions. - The mock exposes the firestore-mock instance directly, letting us pre-populate data and check document existence easily.
内容的提问来源于stack exchange,提问作者Kevin van Schaijk

