单元测试 Angular HttpInterceptor:toHaveBeenCalledWith apears 从未被调用

问题描述 投票:0回答:1

我正在对一个小的错误处理拦截器进行单元测试,我想测试是否已使用参数调用了某个函数。 toHaveBeenCalledWith 函数在控制台中给出“但它从未被调用”。有谁知道为什么会这样?其他测试似乎有效。

Error.interceptor.ts:

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
  constructor() {}

  handleError(error: HttpErrorResponse): Observable<any> {
    let errorMsg = '';
    if (error.status === HTTP_STATUS_ABORTED) {
      errorMsg = 'An client-side or network error occurred';
    } else if (error.status === HttpStatusCode.InternalServerError) {
      errorMsg = 'An internal server error occurred';
    } else {
      errorMsg = `Backend returned code ${error.status}`;
    }

    console.error(errorMsg, ', body was: ', error.error);

    // Return an observable with a user-facing error message.
    return throwError(() => {
      return new Error(errorMsg);
      // return error;
    });
  }

  intercept(
    request: HttpRequest<unknown>,
    next: HttpHandler
  ): Observable<HttpEvent<unknown>> {
    return next.handle(request).pipe(catchError(this.handleError));
  }
}

Error.interceptor.spec.ts:

describe('ErrorInterceptor', () => {
  let client: HttpClient;
  let httpController: HttpTestingController;
  let interceptor: ErrorInterceptor;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        ErrorInterceptor,
        {
          provide: HTTP_INTERCEPTORS,
          useClass: ErrorInterceptor,
          multi: true,
        },
      ],
    });

    client = TestBed.inject(HttpClient);
    httpController = TestBed.inject(HttpTestingController);
    interceptor = TestBed.inject(ErrorInterceptor);
    spyOn(console, 'error');
  });

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

  it('should call handleError with the correct errorObject on code 400', () => {
    spyOn(interceptor, 'handleError').and.callThrough();

    const expectedErrorResponse = new HttpErrorResponse({
      url: '/target',
      status: HttpStatusCode.BadRequest,
      statusText: 'Bad Request',
      error: new ProgressEvent('ERROR', {}),
    });

    client.get('/target').subscribe({
      error: (error: Error) => {
        expect(error).toBeTruthy();
        expect(error).toEqual(new Error('Backend returned code 400'));

        expect(console.error).toHaveBeenCalledWith(
          'Backend returned code 400',
          ', body was: ',
          expectedErrorResponse.error
        );

        expect(interceptor.handleError).toHaveBeenCalledWith(
          expectedErrorResponse
        );
      },
    });

    const httpRequest: HttpRequest<any> = new HttpRequest('GET', '/target');
    const err = new ProgressEvent('ERROR', {});

    httpController.expectOne(httpRequest).error(err, {
      status: HttpStatusCode.BadRequest,
      statusText: 'Bad Request',
    });
  });

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

我尝试测试拦截器是否调用了handleError函数。 我期望

expect(interceptor.handleError).toHaveBeenCalledWith(expectedErrorResponse);
测试它调用该函数并返回真实的期望。

编辑:乔纳斯·露丝发现的修复:

-> 在测试的订阅块中调用完成

  it('should call handleError with the correct errorObject on code 400', (done: DoneFn) => {
    const spyOnHandleError = spyOn(
      interceptor,
      'handleError'
    ).and.callThrough();

    const expectedErrorResponse = new HttpErrorResponse({
      url: '/target',
      status: HttpStatusCode.BadRequest,
      statusText: 'Bad Request',
      error: new ProgressEvent('ERROR', {}),
    });

    client.get('/target').subscribe({
      next: (returnValue) => {
        fail(`Expected error but got "${returnValue}"`);
      },

      error: (error: Error) => {
        expect(error).toBeTruthy();
        expect(error).toEqual(new Error('Backend returned code 400'));

        expect(interceptor.handleError).toHaveBeenCalledWith(
          expectedErrorResponse
        );

        done();
      },

      complete: () => fail('Complete must not be called'),
    });

    const httpRequest: HttpRequest<any> = new HttpRequest('GET', '/target');
    const err = new ProgressEvent('ERROR', {});

    httpController.expectOne(httpRequest).error(err, {
      status: HttpStatusCode.BadRequest,
      statusText: 'Bad Request',
    });
  });

Providers:确保实例相同

  ErrorInterceptor, // instance A
  {
    provide: HTTP_INTERCEPTORS,
    useExisting: ErrorInterceptor, // instance A (will use the same instance)
    multi: true,
  },
],

-> error.interceptor.ts。

.pipe(catchError((err) => this.handleError(err)));```
angular unit-testing jasmine karma-jasmine angular-httpclient-interceptors
1个回答
0
投票

经过大量调查后,我想出了解决您问题的方法。

被监视的拦截器实例与 Angular 正在使用的实例不同。

// error.interceptor.ts

providers: [
  ErrorInterceptor, // instance A (the instance spied)
  {
    provide: HTTP_INTERCEPTORS,
    useClass: ErrorInterceptor, // instance B (another instance used by Angular)
    multi: true,
  },
],

因此您需要在提供程序配置中将

useClass
更改为
useExisting

// error.interceptor.ts

providers: [
  ErrorInterceptor, // instance A
  {
    provide: HTTP_INTERCEPTORS,
    useExisting: ErrorInterceptor, // instance A (will use the same instance)
    multi: true,
  },
],

现在将调用错误响应期望,但监视的

handleError
方法将从
catchError
接收对应于
error
caught
的两个值,因为
handleError
intercept 中以以下方式声明
方法。所以期望不会匹配并失败。

// error.interceptor.ts

.pipe(catchError(this.handleError))

输出(带注释):

Expected spy handleError to have been called with:
          [ HttpErrorResponse({ headers: HttpHeaders({ normalizedNames: Map(  ), lazyUpdate: null, headers: Map(  ) }), status: 400, statusText: 'Bad Request', url: '/target', ok: false, name: 'HttpErrorResponse', message: 'Http failure response for /target: 400 Bad Request', error: [object ProgressEvent] }) ]
        but actual calls were:
          [ 
            HttpErrorResponse({ headers: HttpHeaders({ normalizedNames: Map(  ), lazyUpdate: null, headers: Map(  ) }), status: 400, statusText: 'Bad Request', url: '/target', ok: false, name: 'HttpErrorResponse', message: 'Http failure response for /target: 400 Bad Request', error: [object ProgressEvent] }), 
            // below observable was not expected
            Observable({ source: Observable({ _subscribe: Function }), operator: Function }) 
         ].

我建议两个选项作为满足错误响应期望的解决方案:

(1) 您可以将传递

handleError
方法的方式更改为类似以下代码的方式:

// error.interceptor.ts

.pipe(catchError((err) => this.handleError(err)));

(2) 或者更改间谍和期望的声明方式,但保持其等价性:

// error.interceptor.spec.ts

// add spy into a variable to further use
const spyOnHandleError = spyOn(interceptor, 'handleError').and.callThrough();
// error.interceptor.spec.ts

// break the error response expectation into two expectations
// that use spyOnHandleError to get some useful information

// expect to be called
expect(spyOnHandleError.calls.count()).toBeGreaterThan(0);

// expect the most recent call first argument to match the `expectedErrorResponse`  
expect(spyOnHandleError.calls.mostRecent().args[0]).toEqual(expectedErrorResponse);

通过这些更改,您的测试将成功运行!

最终输出:

✔ Browser application bundle generation complete.
Chrome 113.0.0.0 (Linux x86_64): Executed 2 of 2 SUCCESS (0.033 secs / 0.021 secs)
TOTAL: 2 SUCCESS

完全更新答案

© www.soinside.com 2019 - 2024. All rights reserved.