Angular HttpInterceptor单元测试:toHaveBeenCalledWith提示未调用
问题:HttpInterceptor单元测试中无法验证handleError的调用情况
我在对一个错误处理HttpInterceptor做单元测试,想验证handleError函数是否携带指定参数被调用,但控制台里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(); }); });
我期望通过expect(interceptor.handleError).toHaveBeenCalledWith(expectedErrorResponse);验证函数被正确调用,但未达到预期。
修复方案
在测试的subscribe回调中调用done函数
异步测试需要调用done()通知测试框架异步操作已完成,避免断言在异步回调执行前就被判定失败: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', }); });确保Provider使用同一实例
原来的配置会创建两个ErrorInterceptor实例,测试中spy的实例和实际拦截器使用的实例不一致,改用useExisting复用已注入的实例:providers: [ ErrorInterceptor, // 实例A { provide: HTTP_INTERCEPTORS, useExisting: ErrorInterceptor, // 复用实例A multi: true, }, ],修改interceptor中的catchError调用方式
直接传递this.handleError会丢失this上下文,改用箭头函数绑定正确的上下文:.pipe(catchError((err) => this.handleError(err)));
内容的提问来源于stack exchange,提问作者Harald
相关产品推荐
相关产品推荐

