Angular 11:对 HttpInterceptor 进行单元测试 - 异步计时器或 AfterAll 错误

Posted

技术标签:

【中文标题】Angular 11:对 HttpInterceptor 进行单元测试 - 异步计时器或 AfterAll 错误【英文标题】:Angular 11: Unit testing an HttpInterceptor - async timer or AfterAll errors 【发布时间】:2021-03-10 14:41:08 【问题描述】:

我正在努力让我的 HttpInterceptor 通过我的单元测试,但我遇到了让测试通过的问题。我有 6 个测试,如果我将测试作为“正常”测试运行,当测试在本地完成时,我经常会收到“在 afterAll 中引发错误”,但我的 Azure 管道每次都失败并出现相同的错误。

如果我使用 fakeAsync() 在异步区域中运行测试,每个测试都会收到“1 个计时器仍在队列中”错误。

这是拦截器的代码:

import  HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest from '@angular/common/http';
import  Injectable  from '@angular/core';
import  Observable, throwError  from 'rxjs';
import  retry, tap, catchError  from 'rxjs/operators';
import  HttpServiceError  from '../models/httpServiceError.model';
import  LoggingService, LogLevel  from '../services/logging.service';

@Injectable(
    providedIn: 'root'
)

export class HttpResponseInterceptorService implements HttpInterceptor
  public logLevel!: LogLevel;

  constructor(private readonly loggingService: LoggingService) 
// Intentionally left blank
  

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> 
    return next.handle(request)
      .pipe(
        retry(1),
        tap(() => console.log('ResponseInterceptor called')),
        catchError((error: HttpErrorResponse) => 
          this.handleHttpError(error);
          return throwError(error);
        ));
  

  private handleHttpError(error: HttpErrorResponse): Observable<HttpServiceError> 
    const requestError = new HttpServiceError();    
    
    requestError.errorNumber = error.status;
    requestError.statusMessage = error.statusText;

    switch (error.status) 
      case 401: 
        requestError.friendlyMessage = 'You are not logged in';
        break;
      
      case 403: 
        requestError.friendlyMessage = 'You are not logged in';
        break;
      
      case 404: 
        requestError.friendlyMessage = 'The service failed to respond';
        break;
      
      case 429: 
        requestError.friendlyMessage = 'The service is busy';
        break;
      
      case 500: 
        requestError.friendlyMessage = 'The service is not responding correctly';
        break;
      
      default: 
        requestError.friendlyMessage = 'An error occured retrieving the data';
        break;
      
    

    // TODO replace with some kind of notification
    // window.alert(errorMessage);

    this.loggingService.logWithLevel(JSON.stringify(requestError), LogLevel.Information);

    return throwError(requestError);
  

这是我的规范文件:

import  HttpClient, HttpErrorResponse, HTTP_INTERCEPTORS  from '@angular/common/http';
import  HttpClientTestingModule, HttpTestingController  from '@angular/common/http/testing';
import  fakeAsync, flush, inject, TestBed  from '@angular/core/testing';
import  LoggingService  from '../services/logging.service';

import  HttpResponseInterceptorService  from './httpresponseinterceptor.service';

describe('HttpResponseInterceptorService', () => 
  let interceptor: HttpResponseInterceptorService;
  let httpMock: HttpTestingController;
  let loggingService: LoggingService;
  let httpClient: HttpClient;

  beforeEach(() => 
    TestBed.configureTestingModule(
      imports: [HttpClientTestingModule],
      providers: [
        
          provide: HTTP_INTERCEPTORS,
          useClass: HttpResponseInterceptorService,
          multi: true,
        ,
      ]
    );
    loggingService = TestBed.inject(LoggingService);
    interceptor = TestBed.inject(HttpResponseInterceptorService);
    httpMock = TestBed.inject(HttpTestingController);
    httpClient = TestBed.inject(HttpClient);
  );

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

  afterAll(() => 
    TestBed.resetTestingModule();
  );

  it('should be created', () => 
    expect(interceptor).toBeTruthy();
  );

  it('should call loggingService with a friendlyMessage of An error occured retrieving the data (everything else)', fakeAsync(() => 
    spyOn(loggingService,'logWithLevel');

    const errorResponse = new HttpErrorResponse(
      error: '599 error',
      status: 599, statusText: 'Unknown Error'
    );

    httpClient.get('/api').subscribe();

    let request = httpMock.expectOne('/api');
    request.error(new ErrorEvent('599 First Unknown Error'), errorResponse);

    request = httpMock.expectOne('/api');
    request.error(new ErrorEvent('599 Second Unknown Error'), errorResponse);

    expect(loggingService.logWithLevel).toHaveBeenCalled();
  ));

  it('should call loggingService with a friendlyMessage of You are not logged in (401)', fakeAsync(() => 
    spyOn(loggingService,'logWithLevel');

    const errorResponse = new HttpErrorResponse(
      error: '401 error',
      status: 401, statusText: 'Unauthorized'
    );

    httpClient.get('/api').subscribe();

    let request = httpMock.expectOne('/api');
    request.error(new ErrorEvent('401 First Unauthorized'), errorResponse);

    request = httpMock.expectOne('/api');
    request.error(new ErrorEvent('401 Second Unauthorized'), errorResponse);

    expect(loggingService.logWithLevel).toHaveBeenCalled();
  ));

  it('should call loggingService with a friendlyMessage of You are not logged in (403)', fakeAsync(() => 
    spyOn(loggingService,'logWithLevel');

    const errorResponse = new HttpErrorResponse(
      error: '403 error',
      status: 403, statusText: 'Forbidden'
    );

    httpClient.get('/api').subscribe();

    let request = httpMock.expectOne('/api');
    request.error(new ErrorEvent('403 First Forbidden'), errorResponse);

    request = httpMock.expectOne('/api');
    request.error(new ErrorEvent('403 Second Forbidden'), errorResponse);

    expect(loggingService.logWithLevel).toHaveBeenCalled();
  ));

  it('should call loggingService with a friendlyMessage of The service failed to respond (404)', fakeAsync(() => 
    spyOn(loggingService,'logWithLevel');

    const errorResponse = new HttpErrorResponse(
      error: '404 error',
      status: 404, statusText: 'Not Found'
    );

    httpClient.get('/api').subscribe();

    let request = httpMock.expectOne('/api');
    request.error(new ErrorEvent('404 First Error'), errorResponse);

    request = httpMock.expectOne('/api');
    request.error(new ErrorEvent('404 Second Error'), errorResponse);

    expect(loggingService.logWithLevel).toHaveBeenCalled();
  ));

  it('should call loggingService with a friendlyMessage of The service is busy (429)', fakeAsync(() => 
    spyOn(loggingService,'logWithLevel');

    const errorResponse = new HttpErrorResponse(
      error: '429 error',
      status: 429, statusText: 'Too Many Requests'
    );

    httpClient.get('/api').subscribe();

    let request = httpMock.expectOne('/api');
    request.error(new ErrorEvent('429 First Too Many Requests'), errorResponse);

    request = httpMock.expectOne('/api');
    request.error(new ErrorEvent('429 Second Too Many Requests'), errorResponse);

    expect(loggingService.logWithLevel).toHaveBeenCalled();

  ));

  it('should call loggingService with a friendlyMessage of The service is not responding correctly (500)', fakeAsync(() => 
    spyOn(loggingService,'logWithLevel');

    const errorResponse = new HttpErrorResponse(
      error: '500 error',
      status: 500, statusText: 'Internal Server Error'
    );

    httpClient.get('/api').subscribe();

    let request = httpMock.expectOne('/api');
    request.error(new ErrorEvent('500 First Internal Server Error'), errorResponse);

    request = httpMock.expectOne('/api');
    request.error(new ErrorEvent('500 Second Internal Server Error'), errorResponse);

    expect(loggingService.logWithLevel).toHaveBeenCalled();
  ));

);

似乎有一个未完成的异步调用。我在httpClient.get() 调用之后尝试使用flush(),我尝试在每次调用之后将它放在request = httpMock.expectOne('/api') 之后,但似乎没有任何东西可以真正修复错误。

这也可能是因为我笨拙的按条件进行一次测试的方法。

非常欢迎好的想法和反馈!

谢谢,比亚恩

【问题讨论】:

【参考方案1】:

因此,在对HttpTestingController 进行了一些研究之后,我能够重新创建并重现该错误。解决方案与我之前的答案相差不远,但根本不正确。

subscribe() 之后缺少tick(200),但是当您返回错误时,您必须处理订阅中的错误。

所以这里有一个第一个测试的工作示例。

fit("should call loggingService with a friendlyMessage of An error occured retrieving the data (everything else)", fakeAsync(() => 
    spyOn(loggingService, "logWithLevel");

    const errorResponse = new HttpErrorResponse(
      error: "599 error",
      status: 599,
      statusText: "Unknown Error"
    );

    httpClient
      .get("/api")
      .subscribe(_ => console.log("Good"), err => console.log(err));

    let request = httpMock.expectOne("/api");
    request.error(new ErrorEvent("599 First Unknown Error"), errorResponse);

    request = httpMock.expectOne("/api");
    request.error(new ErrorEvent("599 Second Unknown Error"), errorResponse);

    tick(100);
    expect(loggingService.logWithLevel).toHaveBeenCalled();
  ));

【讨论】:

我刚开始测试您的建议。你完全搞定了!谢谢!

以上是关于Angular 11:对 HttpInterceptor 进行单元测试 - 异步计时器或 AfterAll 错误的主要内容,如果未能解决你的问题,请参考以下文章

Angular 11-数据绑定数组项

angular10兼容IE9

angular10兼容IE9

angular10兼容IE9

Angular 11异步验证器触发真实响应的无限循环请求

Angular 11 自定义 ISBN 验证器响应式表单