Ad

Mocking Dependecies With Services Resulting In Firebase Error

I'm using the following code to mock the dependecy with authservice:

login.component.spec

import { LoginComponent } from "./login.component";
import { ComponentFixture, inject, TestBed } from "@angular/core/testing";
import { async } from "q";
import { MatCardModule } from "@angular/material";
import { AuthService } from "../../services/auth/auth.service";
import { Log } from "@angular/core/testing/src/logger";
import { NO_ERRORS_SCHEMA } from "@angular/core";

class MockAuthService extends AuthService {
  isAuthenticated() {
    return "Mocked";
  }
}

describe("LoginComponent", () => {
  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>;
  let componentService: AuthService;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [LoginComponent],
      providers: [AuthService],
      imports: [MatCardModule]
    });
    TestBed.overrideComponent(LoginComponent, {
      set: { providers: [{ provide: AuthService, useClass: MockAuthService }] }
    });

    fixture = TestBed.createComponent(LoginComponent);
    component = fixture.componentInstance;
    componentService = fixture.debugElement.injector.get(AuthService);
  }));

  it("Service injected via component should be and instance of MockAuthService", () => {
    expect(componentService instanceof MockAuthService).toBeTruthy();
  });
});

login.component

import {Component, OnInit} from '@angular/core';

import {AuthService} from '../../services/auth/auth.service';
import {Router} from '@angular/router';
import {GithubService} from '../../services/github/github.service';
import {Errorcode} from './errorcode.enum';


@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.sass'],

})
export class LoginComponent implements OnInit {
  public loginError: string | boolean = false;

  constructor(public authService: AuthService, public router: Router, private data: GithubService) {
  }

  public signInWithGithub(): void {
    this.authService.loginwithGithubProvider()
      .then(this.loginError = null)
      .catch(err => {
        if (err === Errorcode.FIREBASE_POPUP_CLOSED) {
        this.loginError = 'The popup has been closed before authentication';
        }
        if (err === Errorcode.FIREBASE_REQUEST_EXESS) {
          this.loginError = 'To many requests to the server';
        }
      }
    );
  }

  public logout(): void {
    this.authService.logout();
  }

  ngOnInit() {
  }
}

But if i take a look at the results i keep getting the following error:

Error: StaticInjectorError(DynamicTestModule)[AuthService -> AngularFireAuth]: StaticInjectorError(Platform: core)[AuthService -> AngularFireAuth]: NullInjectorError: No provider for AngularFireAuth! in http://localhost:9876/_karma_webpack_/vendor.js (line 59376)

Any idea's how i might solve this?

Ad

Answer

I have reproduced your issue in a Stackblitz. In the Stackblitz currently the tests are all passing, but you will notice I have commented out your declaration of MockAuthService like so:

// class MockAuthService extends AuthService {
//     isAuthenticated() {
//       return "Mocked";
//     }
// }

and replaced it with:

class MockAuthService implements Partial<AuthService> {
    isAuthenticated() {
      return "Mocked";
    }
    loginwithGithubProvider() {
      return new Promise((resolve, reject) => resolve())
    }
    logout() {}
}

The key difference to notice is I replaced extends with implements.

To reproduce your error, simply comment out my new declaration of MockAuthService, and uncomment out your original one. The error you describe above will reappear.

The reason for this is because when you extend a class you get all the objects, properties, methods, etc of the existing class including the constructor. In your case I'm quite sure the constructor for the original AuthService class has injected AngularFireAuth, just as I did in the Stackblitz in the file support.service.ts where I implemented a stub of AuthService. When you extended the class and then injected it into the TestBed when you overrode the component, then tried to create the component, it attempted to execute the constructor in that original class. However, there was no provider given for AngularFireAuth, therefore it threw the error you see. Simply adding AngularFireAuth to the providers array would not have been the right solution because you are trying to test the component, not the service.

When I want to inject a mock class into a test suite, like you have done here, I usually use implements and then Partial<> so that I don't have to implement all the methods and properties of the original class, and so that I only have mocked what I want to, not drag along the original class implementation details.

There is a pretty good discussion on the differences between implements and extendshere.

Some further notes:

  • I had to add two other mocked services, for Router and GithubService. Note that I did these as spies rather than service class mocks, just to show a different way to do these. I notice you had imported NO_ERRORS_SCHEMA, probably to avoid having to define these. I don't use that schema myself, even though it seems fairly popular, since I don't want to mask errors - I would rather have them exposed and fix them. :)
  • I also added another spec just to show how you might take this a next step by testing part of the signInWithGithub() method.

I hope this helps.

Ad
source: stackoverflow.com
Ad