-
Notifications
You must be signed in to change notification settings - Fork 2.2k
How to mock Firestore in v9 #3095
-
Hello,
I am migrating to Angular 12, Angular Fire 7 and Firebase 9, but I am failing to mock firestore.
I've migrated all my code to the tree shakeable format: ie:
import { doc, getDoc } from "firebase/firestore";
class ProfileDao {
constructor(protected firestore: Firestore) {}
get(id: string) {
const docRef = doc(db, "profile", id);
return getDoc(docRef)
}
}
But now, my tests are failing w/ the message: Error: AngularFireModule has not been provided at getScheduler
Here is an example:
fdescribe("ProfileDao", () => {
let firestore: Firestore;
let profileDao: ProfileDao;
let uid: string;
beforeEach(() => {
firestore = jasmine.createSpyObj("Firestore", ["x"]); // I know it is wrong here.
uid = faker.datatype.uuid();
profileDao = new ProfileDao(firestore);
});
it ("calls firestore with the document with the given id", async () => {
profileDao.get(uid); // The error is thrown here (because getDoc tries to a global something something from my mock
expect(firestore.x).toHaveBeenCalled();
});
Firestore snipet:
function getSchedulers() {
const schedulers = globalThis.ɵAngularFireScheduler;
if (!schedulers) {
throw new Error('AngularFireModule has not been provided');
}
return schedulers;
}
So what is the correct way to mock firestore in v9?
Beta Was this translation helpful? Give feedback.
All reactions
-
👀 3
Replies: 6 comments 11 replies
-
I am having the same issue
I am trying to use ng-mocks
to mock Firestore
, but it isn't working either, same error:
https://github.com/ike18t/ng-mocks
I look at the examples which @jamesdaniels gave, but it doesn't seem helpful:
Beta Was this translation helpful? Give feedback.
All reactions
-
@BBX999 Does not work unfortunately. I believe auto-spies does not create a Provider like how angular wants
I wish MockProvider
from ng-mocks
just works (noticed the commented out line)
Beta Was this translation helpful? Give feedback.
All reactions
-
For now im unit-testing on the live firestore instance
I know it's bad, but this is taking up a lot of time
describe('CommentsComponent', () => { let component: CommentsComponent; let fixture: ComponentFixture<CommentsComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ RouterTestingModule, provideFirebaseApp(() => initializeApp(environment.firebase)), provideFirestore(() => { const firestore = getFirestore(); return firestore; }), ], declarations: [CommentsComponent, MockDirective(NgVarDirective), MockComponent(CommentPosterComponent)], providers: [ MockProvider(ActivatedRoute, { snapshot: MockService(ActivatedRouteSnapshot, { data: { user: { uid: 'test-id', }, }, parent: MockService(ActivatedRouteSnapshot, { parent: MockService(ActivatedRouteSnapshot, { paramMap: { has: () => true, get: () => '', getAll: () => [], keys: [], } as ParamMap, }), }), }), }), ], }).compileComponents(); }); });
Beta Was this translation helpful? Give feedback.
All reactions
-
@BBX999 Does not work unfortunately.
I don't think you are injecting it properly, see here:
You need to use this syntax:
{ provide: ApiService, useValue: createSpyFromClass(ApiService) },
(..as opposed to simply declaring it as a const at the top and using that value as a provider, which it is not).
I think auto-spies should work for you here and save you the time (and complexity!) of integrated tests that use a live database.
Beta Was this translation helpful? Give feedback.
All reactions
-
@BBX999 Does not work unfortunately.
I don't think you are injecting it properly, see here:
You need to use this syntax:
{ provide: ApiService, useValue: createSpyFromClass(ApiService) },
(..as opposed to simply declaring it as a const at the top and using that value as a provider, which it is not).
I think auto-spies should work for you here and save you the time (and complexity!) of integrated tests that use a live database.
Using your proposed syntax doesn't work, unfortunately. It still asks for AngularFireModule to be provided.
Beta Was this translation helpful? Give feedback.
All reactions
-
don't inject Firestore directly into your component, use a service, then mock the service in the test
Beta Was this translation helpful? Give feedback.
All reactions
-
auto-spies
is different from ng-mocks in a way that it doesn't deal with the dom layer, just classes.
But the advantage to auto-spies
is that it provides convenient methods to configure fake observables and promises that are returning from your fake methods.
Instead of using createSpyFromClass
consider using provideAutoSpy
Besides that, I think it would be valuable for you to move all of your firebase logic to another service and out of the component layer.. that way you will simplify your component tests and just return "a fake observable" instead of dealing with the hard mocking of firebase.
And create contract tests for your firebase logic
Same goes for the activatedRoue mocking -
Whenever you want to isolate something, you want to avoid mocking the internals of your dependencies as much as possible (because if they'll change, you'll need to update lots of tests... so it's less efficient)
Whenever the tests are hard to write - it's often a signal that you might want to re-think your design decision
For example, instead of traversing up the parents of the activatedRoute, I'm guessing there is an interesting information on that "grandfather route".. so you might want to save that info in a "local store" or service and inject it to your component and ask it questions..
Again, I don't know the entire context of your code, but these are just a few thoughts that might help
good luck!
Beta Was this translation helpful? Give feedback.
All reactions
-
Hi,
I tried so hard to mock v9 functions such as doc
and docData
with auto-spies createFunctionSpy
and rises Error: AngularFire has not been provided
. Then tried adding AngularFire
from @angular/fire/compat/firestore
to providers without any success.
Spying the service or component under test with provideAutoSpy
won't go much deeper to cover all methods and branches.
Finally, I decided to write full integration tests. This worked for me:
Service
import { Injectable } from '@angular/core'; import { doc, docData, Firestore } from '@angular/fire/firestore'; import { traceUntilFirst } from '@angular/fire/performance'; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class DataService { constructor(private firestore: Firestore) { } get(path: string, ...pathSegments: string[]): Observable<any> { const ref = doc(this.firestore, path, ...pathSegments); return docData(ref).pipe(traceUntilFirst('firestore')); } }
Tests
import { TestBed } from '@angular/core/testing'; import { initializeApp, provideFirebaseApp } from '@angular/fire/app'; import { getFirestore, provideFirestore } from '@angular/fire/firestore'; import { environment } from 'src/environments/environment'; import { DataService } from './data.service'; describe('DataService', () => { let service: DataService; beforeEach(() => { TestBed.configureTestingModule({ imports: [ provideFirebaseApp(() => initializeApp(environment.firebase)), provideFirestore(() => getFirestore()) ] }); service = TestBed.inject(DataService); }); it('should be created', () => { expect(service).toBeTruthy(); }); it('should return the doc if path is valid', (done: DoneFn) => { service .get('items/1') .subscribe((data: unknown) => { expect(data).toBeDefined(); done(); }); }); it('should be undefined if path does not exist', (done: DoneFn) => { service .get('items/999999') .subscribe((data: unknown) => { expect(data).toBeUndefined(); done(); }); }); });
Output
$npm test > app@0.0.0 test > ng test --code-coverage --no-watch --browsers=FirefoxHeadless ✔ Browser application bundle generation complete. 01 05 2022 00:06:42.233:INFO [karma-server]: Karma v6.3.19 server started at http://localhost:9876/ 01 05 2022 00:06:42.236:INFO [launcher]: Launching browsers FirefoxHeadless with concurrency unlimited 01 05 2022 00:06:42.243:INFO [launcher]: Starting browser FirefoxHeadless 01 05 2022 00:06:58.734:INFO [Firefox 91.0 (Linux x86_64)]: Connected on socket 56KwfxRN9Uw_24wzAAAB with id 11166302 Firefox 91.0 (Linux x86_64): Executed 4 of 4 SUCCESS (1.781 secs / 1.7 secs) TOTAL: 4 SUCCESS =============================== Coverage summary =============================== Statements : 100% ( 8/8 ) Branches : 100% ( 0/0 ) Functions : 100% ( 2/2 ) Lines : 100% ( 6/6 ) ================================================================================
I hope it helps.
Beta Was this translation helpful? Give feedback.
All reactions
-
Any update on this one?
Beta Was this translation helpful? Give feedback.
All reactions
-
Any progress here?
Beta Was this translation helpful? Give feedback.
All reactions
-
Beta Was this translation helpful? Give feedback.
All reactions
-
you don't test the service directly, you test the component where the service is provided and make assertions about what the user behavior should be
by doing this, you'll be testing the intention of the code rather than the implementation details
Beta Was this translation helpful? Give feedback.