Michael Andorfer
Michael Andorfer

Reputation: 1800

How to correctly test Angular4 application with Auth0 integration?

I am currently working on an Angular4 web application which uses Auth0 for authentication.

While the authentication works as expected the integration of Auth0 has broken (lets fail) the default tests (Karma unit tests) of my application.

My code looks as follows:

// app.component.ts

/*
 * Angular 2 decorators and services
 */
import {
  Component,
  ViewEncapsulation
} from '@angular/core';

import { Auth } from './auth.service';

/*
 * App Component
 * Top Level Component
 */
@Component({
  selector: 'app',
  providers: [ Auth ],
  encapsulation: ViewEncapsulation.None,
  styleUrls: [
    './app.component.scss'
  ],
  template: `
    <div class="container-fluid">
      <router-outlet></router-outlet>
    </div>
  `
})
export class AppComponent {
  public angularclassLogo = 'assets/img/ExampleApp_smallLogo.png';
  public name = 'ExampleApp';
  public url = 'https://www.example.com/';

  constructor(private auth: Auth) {
    this.auth.handleAuth();
  }
}

// auth.service.ts

import { Injectable } from '@angular/core';
import { tokenNotExpired } from 'angular2-jwt';
import { Router } from '@angular/router';
import { Http, Headers, RequestOptions, RequestMethod, Response } from '@angular/http';
import 'rxjs/add/operator/toPromise';
import 'rxjs/add/operator/filter';
import Auth0Lock from 'auth0-lock';
import Auth0 from 'auth0-js';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

import { myConfig, postConfig, necessaryRoles } from './auth.config';

// Avoid name not found warnings
// declare var auth0: any;

@Injectable()
export class Auth {
  public lock = new Auth0Lock(myConfig.clientID, myConfig.domain, myConfig.lock);
  public userProfile: any;
  public idToken: string;
  public signUpIncomplete: boolean;

  // Configure Auth0
  private auth0 = new Auth0.WebAuth({
    domain: myConfig.domain,
    clientID: myConfig.clientID,
    redirectUri: myConfig.redirectUri,
    responseType: myConfig.responseType
  });

  // Create a stream of logged in status to communicate throughout app
  private loggedIn: boolean;
  private loggedIn$ = new BehaviorSubject<boolean>(this.loggedIn);

  constructor(private router: Router, private http: Http) {
    // Set userProfile attribute of already saved profile
    this.userProfile = JSON.parse(localStorage.getItem('profile'));
  }
  ...
}

// app.component.spec

import { NO_ERRORS_SCHEMA } from '@angular/core';
import {
  async,
  TestBed,
  ComponentFixture
} from '@angular/core/testing';
import {
  BaseRequestOptions,
  HttpModule,
  Http,
  XHRBackend,
} from '@angular/http';
import { RouterTestingModule } from '@angular/router/testing';
import { MockBackend } from '@angular/http/testing';

// Load the implementations that should be tested
import { AppComponent } from './app.component';
import { AppState } from './app.service';
import { Auth } from './auth.service';

describe(`App`, () => {
  let comp: AppComponent;
  let fixture: ComponentFixture<AppComponent>;

  // async beforeEach
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ AppComponent ],
      imports: [RouterTestingModule, HttpModule],
      schemas: [NO_ERRORS_SCHEMA],
      providers: [
        AppState,
        Auth,
        MockBackend,
        BaseRequestOptions,
        {
          provide: Http,
          deps: [MockBackend, BaseRequestOptions],
          useFactory:
            (backend: XHRBackend, defaultOptions: BaseRequestOptions) => {
              return new Http(backend, defaultOptions);
            }
        }
      ]
    })
    .compileComponents(); // compile template and css
  }));

  // synchronous beforeEach
  beforeEach(() => {
    fixture = TestBed.createComponent(AppComponent);
    comp    = fixture.componentInstance;

    fixture.detectChanges(); // trigger initial data binding
  });

  it(`should be readly initialized`, () => {
    expect(fixture).toBeDefined();
    expect(comp).toBeDefined();
  });

  it(`should be ExampleApp`, () => {
    expect(comp.url).toEqual('https://www.example.com/');
    expect(comp.angularclassLogo).toEqual('assets/img/ExampleApp_smallLogo.png');
    expect(comp.name).toEqual('ExampleApp');
  });
});

The problem is that both App: should be readly initialized and App: should be MyApp fail with Cannot read property 'WebAuth' of undefined although WebAuth is defined in auth.service.ts which is then imported in app.component.spec.

Am I missing any import or declaration?

Upvotes: 0

Views: 1390

Answers (1)

Michael Andorfer
Michael Andorfer

Reputation: 1800

I finally solved the questions by myself.

I had to create a mock for the Auth service.

Further, I had to override the App component so that it uses that mock object instead of the real Auth service.

Therefore the solution looks as follows:

import { NO_ERRORS_SCHEMA } from '@angular/core';
import {
  async,
  TestBed,
  ComponentFixture
} from '@angular/core/testing';
import {
  BaseRequestOptions,
  HttpModule,
  Http,
  XHRBackend,
} from '@angular/http';
import { RouterTestingModule } from '@angular/router/testing';
import { MockBackend } from '@angular/http/testing';

// Load the implementations that should be tested
import { AppComponent } from './app.component';
import { Auth } from './auth.service';

// Mock our Auth service
export class MockAuthService {
  public handleAuth(): void {
    return;
  }
}

describe(`App`, () => {
  let comp: AppComponent;
  let fixture: ComponentFixture<AppComponent>;

  // async beforeEach
  beforeEach(async(() => {
    TestBed
      .configureTestingModule({
        declarations: [ AppComponent ],
        imports: [RouterTestingModule, HttpModule],
        schemas: [NO_ERRORS_SCHEMA],
        providers: [
          MockBackend,
          BaseRequestOptions,
          {
            provide: Http,
            deps: [MockBackend, BaseRequestOptions],
            useFactory: (backend: XHRBackend, defaultOptions: BaseRequestOptions) => {
              return new Http(backend, defaultOptions);
            }
          }
        ]
      })
      .overrideComponent(AppComponent, {
        set: {
          providers: [{ provide: Auth, useValue: new MockAuthService() }]
        }
      })
      .compileComponents();

  }));

  // synchronous beforeEach
  beforeEach(() => {
    fixture = TestBed.createComponent(AppComponent);
    comp    = fixture.componentInstance;

    fixture.detectChanges(); // trigger initial data binding
  });

  it(`should be readly initialized`, () => {
    expect(fixture).toBeDefined();
    expect(comp).toBeDefined();
  });

  it(`should be ExampleApp`, () => {
    expect(comp.url).toEqual('https://www.example.com/');
    expect(comp.angularclassLogo).toEqual('assets/img/ExampleApp_smallLogo.png');
    expect(comp.name).toEqual('ExampleApp');
  });
});

Upvotes: 1

Related Questions