3

I am trying to use Karma and Jasmine to test my Angular5 that uses Firestore. However when I do "ng test", I get an error saying "NullInjectorError: No provider for AngularFirestore/AngularFireAuth/AuthService!". Following the post at NullInjectorError: No provider for AngularFirestore, I added AngularFirestore, AngularFireAuth, and AuthService to provider in app.module.ts, but that did not resolve the issue.

app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AuthService} from './core/auth.service';

// Routing and routes import
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

// Authentication Module
import { CoreModule } from './core/core.module';

// Firebase imports
import { environment } from '../environments/environment';
import { AngularFireModule } from 'angularfire2';
import { AngularFirestoreModule } from 'angularfire2/firestore';
import { AngularFireAuthModule } from 'angularfire2/auth';
import { AngularFireAuth } from 'angularfire2/auth';
import { AngularFireStorageModule } from 'angularfire2/storage';
import { UserProfileComponent } from './user-profile/user-profile.component';
import { ProjectsComponent } from './projects/projects.component';
import { AngularFirestore } from 'angularfire2/firestore';

@NgModule({
  declarations: [
    AppComponent,
    UserProfileComponent,
    ProjectsComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    AngularFireModule.initializeApp(environment.firebase, "life-planner"),
    AngularFirestoreModule,
    AngularFireAuthModule,
    AngularFireStorageModule,
    FormsModule,
    CoreModule,
    AuthService,
    AngularFirestore,
    AngularFireAuth
  ],
  providers: [
    AngularFirestore,
    AuthService,
    AngularFireAuth,

  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

tsconfig.json

{
  "compileOnSave": false,
  "compilerOptions": {
    "paths": {
      "@angular/*": [
        "../node_modules/@angular/*"
      ]
    },
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es5",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2017",
      "dom"
    ]
  }
}

app.component.spec.ts (the code for tests):

import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { RouterTestingModule } from '@angular/router/testing';
import { 
  AngularFirestore,
  AngularFirestoreCollection,
  AngularFirestoreDocument 
} from 'angularfire2/firestore';
import { NgModule } from '@angular/core';

describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ AppComponent ],
      imports: [ 
        RouterTestingModule
      ]
    }).compileComponents();
  }));
  it('should create the app', async(() => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.debugElement.componentInstance;
    expect(app).toBeTruthy();
  }));
  it(`should have as title 'app'`, async(() => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.debugElement.componentInstance;
    expect(app.title).toEqual('app');
  }));
  it('should render title in a h1 tag', async(() => {
    const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    const compiled = fixture.debugElement.nativeElement;
    expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
  }));
});

Any help is appreciated, thanks in advance.

Rentian Dong
  • 101
  • 1
  • 8
  • When you run test you work with testing module and you have to include all things there `TestBed.configureTestingModule` – yurzui Mar 03 '18 at 17:21

2 Answers2

3

The service AngularFirestore relies on initialization of AngularFireModule and import of AngularFirestoreModule which you have done for your actual code.

For the test, you don't have that setup. Which is correct in the sense that ideally you shouldn't call out to actual firestore database from your test. So the solution is you need to stub the service.

const FirestoreStub = {
collection: (name: string) => ({
  doc: (_id: string) => ({
    valueChanges: () => new BehaviorSubject({ foo: 'bar' }),
    set: (_d: any) => new Promise((resolve, _reject) => resolve()),
  }),
}),

};

Add whatever the methods you are using, this example doesn't have all available methods stubbed.

Then provide that stub:

TestBed.configureTestingModule({
  providers: [
    { provide: AngularFirestore, useValue: FirestoreStub },
  ],
});

Credit goes to @ksaifullah

emanuel.virca
  • 598
  • 1
  • 6
  • 13
  • I am stuck here, I am trying to make Karma run buy it alway fails saying "NullInjectorError: No provider for AngularFirestore". I find that the only answer every where its this. But I can't make it work. – Pablo Palacios Jan 27 '19 at 14:27
  • 1
    Ho I found it, this is the answer. Now I have a "TypeError: this.afs.collection(...).snapshotChanges is not a function". Any idea? – Pablo Palacios Jan 27 '19 at 14:40
0

The accepted answer is correct but, if one wants, he/she can call out to actual firestore database from your test. Its just necessary to configure TestBed this a way:

TestBed.configureTestingModule({
        imports: [
            AngularFireAuthModule,
            AngularFirestoreModule,
            AngularFireStorageModule,
            AngularFireModule.initializeApp(Config.firebaseConfig),
        ],
        providers: [AngularFirestore]
    });

So... in fact, you can run your unit tests, using Karma and Jasmine, against your real database.

Nowdeen
  • 1,401
  • 14
  • 20
  • This is good info. I'm on Angular 16.1.4 and @angular/fire of 7.6.1. I have configured the app to use emulators when not a prod build and real firestore when it is prod. All access to firestore is thru a service which is injected into most components. So while I think configureTestingModule will work (different semantics, but same idea) ... not sure if I need that in all spec.ts files or just the one for the service. Probably would know if I understood the ng test scope better. – Michael Casile Jul 19 '23 at 00:41