如何使用 Auth0 集成正确测试 Angular4 应用程序?

How to correctly test Angular4 application with Auth0 integration?

我目前正在开发一个使用 Auth0 进行身份验证的 Angular4 网络应用程序。

虽然身份验证按预期工作,但 Auth0 的集成已破坏(让我们失败)我的应用程序的默认测试(Karma 单元测试)。

我的代码如下所示:

// 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');
  });
});

问题是App: should be ready initializedApp: should be MyApp 都失败了 Cannot read property 'WebAuth' of undefined 虽然WebAuth 在 auth.service.ts 中定义,然后在 app.component.spec.

中导入

我是否遗漏了任何导入或声明?

终于自己解决了问题

我必须为 Auth 服务创建一个模拟。

此外,我必须覆盖 App 组件,以便它使用该模拟对象而不是真正的 Auth 服务。

因此解决方案如下所示:

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