簡體   English   中英

[Angular 7+]:如何在另一個服務規范中對服務進行單元測試?

[英][Angular 7+]: How to unit test a service inside another service spec?

我有這兩個服務文件,其中一個包含在另一個文件中。

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

驗證服務

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

我的問題:如何在另一個可注射文件中包含/測試一個可注射(服務)?

我認為您需要為此提供一個模擬,除非您想進行集成測試。 例如,您嘗試通過僅在providers: [...]包括AuthService來解決此問題的方法providers: [...]列表將不起作用,除非您還添加了必要的文件以構建AuthService (即,您需要將ToastrService包括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

});

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM