我正在对一个小的错误处理拦截器进行单元测试,我想测试是否已使用参数调用了某个函数。 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 正在使用的实例不同。
// 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
完全更新答案