Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

How to mock Firestore in v9 #3095

Unanswered
aronrodrigues asked this question in Q&A
Discussion options

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?

You must be logged in to vote

Replies: 6 comments 11 replies

Comment options

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:

https://github.com/angular/angularfire/blob/master/samples/modular/src/app/firestore/firestore.component.spec.ts

You must be logged in to vote
10 replies
Comment options

@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)

image

image

Comment options

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();
 });
});

image

Comment options

@BBX999 Does not work unfortunately.

I don't think you are injecting it properly, see here:

https://github.com/hirezio/auto-spies/tree/master/packages/jasmine-auto-spies#-angular-developers---use-testbedinjectany

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.

Comment options

@BBX999 Does not work unfortunately.

I don't think you are injecting it properly, see here:

https://github.com/hirezio/auto-spies/tree/master/packages/jasmine-auto-spies#-angular-developers---use-testbedinjectany

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.

Comment options

don't inject Firestore directly into your component, use a service, then mock the service in the test

Comment options

@alexandrucurcubat

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!

You must be logged in to vote
0 replies
Comment options

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.

You must be logged in to vote
0 replies
Comment options

Any update on this one?

You must be logged in to vote
0 replies
Comment options

Any progress here?

You must be logged in to vote
0 replies
Comment options

Exactly what I’m doing. But how do I test the service??
...
On Thu, Jul 4, 2024 at 1:58 PM Mark Goho ***@***.***> wrote: don't inject Firestore directly into your component, use a service, then mock the service in the test — Reply to this email directly, view it on GitHub <#3095 (reply in thread)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AAYKG34MGSPY7VHW3LXX74LZKWZPHAVCNFSM6AAAAABKMDKYROVHI2DSMVQWIX3LMV43SRDJONRXK43TNFXW4Q3PNVWWK3TUHM4TSNRSGMZDS> . You are receiving this because you authored the thread.Message ID: ***@***.***>
You must be logged in to vote
1 reply
Comment options

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

AltStyle によって変換されたページ (->オリジナル) /