Angular 11:对 HttpInterceptor 进行单元测试 - 异步计时器或 AfterAll 错误
Angular 11: Unit testing an HttpInterceptor - async timer or AfterAll errors
我正在努力让我的 HttpInterceptor 通过我的单元测试,但我 运行 遇到了让测试通过的问题。我有 6 个测试,如果我 运行 测试作为“正常”测试,当测试在本地完成时,我经常得到“A error was throwed in 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')
之后,每次调用后似乎都无法真正修复错误。
这也可能是我笨拙的每个条件一个测试的方法。
非常欢迎好的想法和反馈!
谢谢,Bjarne
因此,在对 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();
}));
我正在努力让我的 HttpInterceptor 通过我的单元测试,但我 运行 遇到了让测试通过的问题。我有 6 个测试,如果我 运行 测试作为“正常”测试,当测试在本地完成时,我经常得到“A error was throwed in 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')
之后,每次调用后似乎都无法真正修复错误。
这也可能是我笨拙的每个条件一个测试的方法。
非常欢迎好的想法和反馈!
谢谢,Bjarne
因此,在对 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();
}));