Failed to find firebase ui widget on page - Angular test environment

287 Views Asked by At

I am writing tests for my angular web application that contains a page with a firebase UI element. There are two tests, one that ensures the page loads and one that ensures the firebaseUI component loads correctly:

authentication.component.spec.ts

/*eslint-env jasmine*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { AuthenticationComponent } from './authentication.component';
import { FirebaseService } from '../services/firebase.service';

describe('AuthenticationComponent_Logged_Out', () => {
  let component: AuthenticationComponent;
  let fixture: ComponentFixture<AuthenticationComponent>;
  let service;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ AuthenticationComponent ],
      schemas: [ NO_ERRORS_SCHEMA ]
    }).compileComponents();
    service = TestBed.inject(FirebaseService);
    fixture = TestBed.createComponent(AuthenticationComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should render auth ui', () => {
    const compiled = fixture.nativeElement;
    expect(compiled.querySelector("#firebaseui_auth_container")).toBeTruthy();
  });

  afterEach(async () => {
    service.ui.reset();
  });
});

and with a template file of:

<script src="https://www.gstatic.com/firebasejs/ui/4.8.0/firebase-ui-auth.js"></script>
<link type="text/css" rel="stylesheet" href="https://www.gstatic.com/firebasejs/ui/4.8.0/firebase-ui-auth.css" />

<div id="authentication-wrapper">
    <h1>Please sign in below to access your quotes!</h1>
    <div id="firebaseui_auth_container"></div>
    <div id="loader">Loading...</div>
</div>

and a class of:

import { Component } from '@angular/core';
import { FirebaseService } from '../services/firebase.service';

@Component({
  selector: 'app-authentication',
  templateUrl: './authentication.component.html',
  styleUrls: ['./authentication.component.css']
})
export class AuthenticationComponent {

  constructor (private fbService: FirebaseService) {
    sessionStorage.removeItem('displayed_random');
    // If user logged in, redirect to feed
    if (fbService.currentUser) {
      window.location.href = "/feed";
    } else {
      this.fbService.instantiateUi();
    }
  }
}

The service that actually loads the firebase ui is:

firebase.service.ts

import { Injectable } from '@angular/core';
import firebase from "firebase/app";
import * as firebaseui from "firebaseui";
import { config } from './config';
import 'firebase/database';
import 'firebase/auth';
firebase.initializeApp(config);

@Injectable({
  providedIn: 'root'
})
export class FirebaseService {
  currentUser: string;
  auth = firebase.auth();
  ui = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(this.auth);

  constructor () {
    const username = sessionStorage.getItem('username');
    if (username) {
      this.currentUser = username;
    }
  }

  signoutUser () {
    this.auth.signOut();
    this.currentUser = undefined;
    if (sessionStorage.getItem('username')) {
      sessionStorage.removeItem('username');
    }
  }

  getRef (path) {
    return firebase.database().ref(path);
  }

  instantiateUi () {
    this.ui.start("#firebaseui_auth_container", {
      callbacks: {
        signInSuccessWithAuthResult: (authResult) => {
          // Save username in storage
          sessionStorage.setItem('username', authResult.user.displayName);
          return true;
        },
        uiShown: () => {
          // The widget is rendered, hide the loader.
          document.getElementById('loader').style.display = 'none';
        }
      },
      // Will use popup for IDP Providers sign-in flow instead of the default, redirect.
      signInFlow: 'popup',
      signInSuccessUrl: 'feed',
      signInOptions: [
        {
          provider: firebase.auth.GoogleAuthProvider.PROVIDER_ID,
          customParameters: {
            prompt: 'select_account' // Forces account selection even when only one account is available.
          }
        },
        firebase.auth.EmailAuthProvider.PROVIDER_ID
      ]
    });
  }
}

Now, running the application in a normal dev server environment (i.e. ng serve), the UI is created as expected. However, in the test, it does not get generated properly for some reason and the UI fails to be created. enter image description here

enter image description here

What should I be doing to allow the test framework to find the firebaseui auth container? I have obviously tried to inject the service multiple times and reset the UI after each test but with no joy. I'm guessing that it's to do with compiled being undefined for some reason, which is also strange as that logic works on my other tests.

1

There are 1 best solutions below

5
On

Based on your provided files, we can make a few approaches to test your component in this case. I will mock up your FirebaseService because we should trust that this external dependency is working well.

We should write unite test to lock the code, and if something else on our source code change will break the tests.

Tests scope

We should write minimal around four tests to lock àuthentication.component.ts

  • it should create
  • it should be empty the displayed_random value from sessionStorage
  • execute location href when currentUser is truthy.
  • call fbService function when currentUser is falsy.
constructor (private fbService: FirebaseService) {
    sessionStorage.removeItem('displayed_random');
    // If user logged in, redirect to feed
    if (fbService.currentUser) {     
      window.location.href = "/feed";
    } else {
      this.fbService.instantiateUi();
    }
  }

when the current user is true

Inside your authentication.component.spec.ts

describe('AuthenticationComponent with current user', () => {
beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ AuthenticationComponent ],
      providers: [
        {
          provide: FirebaseService,
          useValue: {
             currentUser: true,
             instantiateUi: () => null
          }
        }
      ],
      schemas: [ NO_ERRORS_SCHEMA ]
    }).compileComponents();
    service = TestBed.inject(FirebaseService);
    fixture = TestBed.createComponent(AuthenticationComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });
it('should create', () => {
  expect(component).toBeTruthy();
});
it('should be session storage displayed_random empty on start', () => {
  expect(sessionStorage.getItem('displayed_random').toBeFalsy();
});
it('should change href value to feed', () => {
   expect(window.location.href).toBe('/feed');
});

when the current user is false

Inside your authentication.component.spec.ts

describe('AuthenticationComponent without current user', () => {
beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ AuthenticationComponent ],
      providers: [
        {
          provide: FirebaseService,
          useValue: {
             currentUser: false,
             instantiateUi: () => null
          }
        }
      ],
      schemas: [ NO_ERRORS_SCHEMA ]
    }).compileComponents();
    service = TestBed.inject(FirebaseService);
    fixture = TestBed.createComponent(AuthenticationComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });
it('should create', () => {
  expect(component).toBeTruthy();
});
it('should call instantiateUI when no current user', () => {
   const instantiateUiSpy = spyOn(service, 'instantiateUi');
   expect(instantiateUiSpy).toHaveBeenCalled();
});