[Angular 7+]:如何在另一个服务规范中对服务进行单元测试?

时间:2019-05-20 13:08:13

标签: angular testing service injectable

我有这两个服务文件,其中一个包含在另一个文件中。

app.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';

import { throwError, Observable } from 'rxjs';

import { catchError, map } from 'rxjs/operators';

import { Router } from '@angular/router';

import { environment } from '../../environments/environment';

import { AuthService } from '../_services/auth.service';

@Injectable({
    providedIn: 'root'
})
export class AppService {

    protected _apiURL = environment.apiURL;
    // Define URLs here
    _overviewURL: string;
    _deleteUserUrl: string;

    constructor(private http: HttpClient,
                private _router: Router,
                private _authService: AuthService) {
        this.setApiUrl();
    }

    /* Begin: Misc services */
    /**
     * @description Sets the header for each request
     * @param authorize Flag to whether include authorization token in headers or not
     * @returns - Header consisting of Authorization token & Content-type
     */
    setHeaders(authorize: boolean = false) {
        const headers: any = {};
        headers['Content-Type'] = 'application/json';

        if (authorize && this._authService.isAuthenticated()) {
            const authenticated = this._authService.getAuthenticatedUser();
            headers.Authorization = `Bearer ${authenticated.idToken}`;
        }
        return {
            headers: new HttpHeaders(headers)
        };
    }

    /**
     * @description Sets all the service URLs with the respective endpoints
     */
    setApiUrl() {
        this._overviewURL = this._apiURL + 'overview';
        this._deleteUserUrl = this._apiURL + 'user/delete';
    }

    /**
     * @description Gets the user overview page details based on BGtOccName & BGtID
     * @param params - consists of BGtOccName & BGtId (BG Occupation Name & BG Occupation ID).
     * Refer BG Docs: https://dev.burning-glass.com/docs/versions/3.3/getting-started
     */
    getOverviewPageInfo(params: any) {
        return this.http.post(this._overviewURL, params, this.setHeaders())
            .pipe(
                map(this.handleResponse),
                catchError(this.handleError)
            );
    }

    /**
     * @description Delete an authenticated user
     * @param user User object from localStorage
     */
    deleteUser(user: any) {
        return this.http.post(this._deleteUserUrl, user, this.setHeaders(true))
            .pipe(
                map(this.handleResponse),
                catchError(this.handleError)
            );
    }


    /**
     * @description processes observable response
     * @param res - takes in the response object
     * @returns - data object
     */
    private handleResponse = (res: any) => {
        return res.data || {};
    }

    /**
     * @description processes observable error
     * @param error - takes in the error object
     * @returns - error object
     */
    private handleError = (error: Response | any) => {
        console.error(error.error.message || error.message);
        const errorMsg = error.error.message || error.message;
        if (errorMsg === 'Invalid token') { this._router.navigate(['/login']); localStorage.removeItem('loggedUser'); }
        return throwError(error.error.error);
    }
}

auth.service.ts

 import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { environment } from '../../environments/environment';
import * as auth0 from 'auth0-js';

import { ToastrService } from 'ngx-toastr';

@Injectable({
    providedIn: 'root'
})
export class AuthService {

  private _idToken: string;
  private _accessToken: string;
  private _expiresAt: number;
  private auth0User: any;

  auth0 = new auth0.WebAuth({
    clientID: environment.AUTH0_CLIENTID,
    domain: environment.AUTH0_DOMAIN,
    responseType: 'token id_token',
    redirectUri: environment.AUTH0_REDIRECT_URI
  });

  constructor(  public router: Router,
                private _toastr: ToastrService) {
    this.auth0User = JSON.parse(localStorage.getItem('auth0User'));
    this._idToken = (this.auth0User && this.auth0User.idToken) ? this.auth0User.idToken : '';
    this._accessToken = (this.auth0User && this.auth0User.accessToken) ? this.auth0User.accessToken : '';
    this._expiresAt = (this.auth0User && this.auth0User.expiresAt) ? this.auth0User.expiresAt : 0;
  }

  get accessToken(): string {
    return this._accessToken;
  }

  get idToken(): string {
    return this._idToken;
  }

  public login(): void {
    this.auth0.authorize();
  }

  public handleAuthentication(): void {
    this.auth0.parseHash((err, authResult) => {
      if (authResult && authResult.accessToken && authResult.idToken) {
        this.localLogin(authResult);
        this.router.navigate(['/home']);
        this._toastr.success(`You have been logged in!`, `Success`);
      } else if (err) {
        this.router.navigate(['/home']);
        this._toastr.error(`Invalid login`, `Failed`);
      }
    });
  }

  private localLogin(authResult): void {
    // Set the time that the access token will expire at
    const expiresAt = (authResult.expiresIn * 1000) + Date.now();
    this._accessToken = authResult.accessToken;
    this._idToken = authResult.idToken;
    this._expiresAt = expiresAt;
    let auth0User: any = localStorage.getItem('auth0User');
    if (auth0User) {
        auth0User = JSON.parse(auth0User);
        auth0User.idToken = authResult.idToken;
        auth0User.expiresAt = expiresAt;
        auth0User.accessToken = authResult.accessToken;
        localStorage.setItem('auth0User', JSON.stringify(auth0User));
    } else {
        localStorage.setItem('auth0User', JSON.stringify({
            idToken: authResult.idToken,
            expiresAt: expiresAt,
            accessToken: authResult.accessToken,
            idTokenPayload: authResult.idTokenPayload
        }));
    }
  }

  public renewTokens(): void {
    this.auth0.checkSession({}, (err, authResult) => {
       if (authResult && authResult.accessToken && authResult.idToken) {
         this.localLogin(authResult);
       } else if (err) {
         this._toastr.error(`Could not get a new token (${err.error}: ${err.error_description}).`, `Failed`);
         this.logout();
       }
    });
  }

  public logout(): void {
    // Remove tokens and expiry time
    this._accessToken = '';
    this._idToken = '';
    this._expiresAt = 0;
    localStorage.removeItem('auth0User');

    this.auth0.logout({
      returnTo: window.location.origin
    });
    this._toastr.success(`You have been logged out!`, `Success`);
  }

  public isAuthenticated(): boolean {
    // Check whether the current time is past the
    // access token's expiry time
    return this._accessToken && Date.now() < this._expiresAt;
  }

  public getAuthenticatedUser() {
      if (localStorage.getItem('auth0User')) {
          return JSON.parse(localStorage.getItem('auth0User'));
      } else {
          return null;
      }
  }

}

每当我运行app.service.spec.ts测试文件时,它都会引发与任何代码无关的错误。

app.service.spec.ts

import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { RouterTestingModule } from '@angular/router/testing';

import { AppService } from './app.service';

describe('AppService', () => {
    let service: AppService;
    let httpMock: HttpTestingController;

    beforeEach(() => TestBed.configureTestingModule({
        imports: [
            HttpClientTestingModule,
            RouterTestingModule
        ],
        providers: [AppService]
    }));

    beforeEach(() => {
        service = TestBed.get(AppService);
        httpMock = TestBed.get(HttpTestingController);
    });

    it('should be created', () => {
        expect(service).toBeTruthy();
    });

    afterEach(() => {
        httpMock.verify();
    });

    it('should retrieve overview details', () => {
        const postParams = {
            'designation': 'Fire Chief / Marshal',
            'id': '378'
        };

        const overviewDetails = {
            'data': {
                'highSalary': 115511.77,
                'estimatedSalary': 98935,
                'nationalSalary': 98498.34,
                'skills': [
                    {
                        'name': 'JavaScript Object Notation (JSON)',
                        'description': 'In computing, JavaScript Object Notation or JSON ( JAY-sn), is an open-standard ' +
                                        'file format that uses human-readable text to transmit data objects consisting of' +
                                        'attributevalue pairs and array data types (or any other serializable value).',
                        'count': 45084
                    },
                    {
                        'name': 'Software Architecture',
                        'description': 'Software architecture refers to the high level structures of a software system,' +
                        'the discipline of creating such structures, and the documentation of these structures.',
                        'count': 42676
                    }
                ],
                'careers': [
                    {
                        'name': 'Chief Executive Officer',
                        'meanSalaryDiff': 11347.74
                    },
                    {
                        'name': 'Database Architect',
                        'meanSalaryDiff': 7699.84
                    }
                ]
            }
        };

        service.getOverviewPageInfo(postParams).subscribe(overview => {
            expect(overview).toEqual(overviewDetails.data);
        });

        const req = httpMock.expectOne(service._overviewURL);

        expect(req.request.method).toBe('POST');

        req.flush(overviewDetails);
    });
});

但是如果删除这些行

if (authorize && this._authService.isAuthenticated()) {
            const authenticated = this._authService.getAuthenticatedUser();
            headers.Authorization = `Bearer ${authenticated.idToken}`;
        }

从app.service.ts文件中,然后所有测试都可以正常工作(如您所见,它正在调用authService函数)。

我试图将authService包含在app.service.spec.ts的提供程序中,如下所示,但是没有运气。 :(

beforeEach(() => TestBed.configureTestingModule({
        imports: [
            HttpClientTestingModule,
            RouterTestingModule
        ],
        providers: [AppService, AuthService]
    }));

我的问题:如何在另一个可注射文件中包含/测试一个可注射(服务)?

1 个答案:

答案 0 :(得分:1)

除非您要进行集成测试,否则我认为您需要为其提供一个模拟。例如,您尝试仅通过在AuthService列表中包含providers: [...]来解决此问题的方法将不起作用,除非您还添加了必要的文件以构造AuthService(即,需要将ToastrService包括在您的提供商列表中。

我的建议是通过告诉AuthService使用您的模拟而不是实际的TestBed来模拟测试中的AuthService。这是我所拥有的,它通过了测试:

// in app.service.spec.ts

//imports

describe('AppService', () => {
  let service: AppService;
  let httpMock: HttpTestingController;

  // Add your mock and all the methods/values you need.
  // You can Spy on this object if you need to change the return values 
  //  for different tests
  let mockAuthSerice: any = {
    isAuthenticated: () => true,
    getAuthenticatedUser: () => {
      return { user: 'bob', idToken: 'token' }
    },
  };

  beforeEach(() => TestBed.configureTestingModule({
    imports: [
      HttpClientTestingModule,
      RouterTestingModule
    ],
    // Provide AuthServide but tell Angular to use the mock instead
    providers: [
      AppService,
      { provide: AuthService, useValue: mockAuthSerice }
    ]
  }));

  // ...rest of test file

});