Angular 10:使用 HttpInterceptor 进行单元测试,修改未获得 HttpResponse 的响应
Angular 10: Unit Testing with a HttpInterceptor that modifies the response not getting an HttpResponse
如果在 SO 周围搜索答案,到目前为止,我尝试过的所有内容都会产生相同的缺失信息。
这是 运行ning Angular 10 与 Karma/Jasmine 的最新版本。
本质上,我有一个 HTTP 拦截器正在查看 return 对象的内容类型。如果是json,照常继续,如果是html...然后抛出错误。
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
HttpResponse
} from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { httpStatusCodes } from '../../../shared/enums/httpStatusCodes.enum';
import { errorResponse } from './../../../shared/models/errorResponse.model';
@Injectable()
export class WafErrorInterceptor implements HttpInterceptor {
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
map((event: HttpEvent<any>) => {
console.log(event instanceof HttpResponse);
if (
event instanceof HttpResponse &&
event.headers.has('content-type') &&
event.headers.get('content-type') === 'application/json'
) {
return event;
}
const throwErrorResponse = new errorResponse(
httpStatusCodes.WAF_ERROR,
'99999',
event instanceof HttpResponse
? event.body
: 'unknown error occurred'
);
throw throwErrorResponse;
})
);
}
}
然后在我的单元测试中我 运行 这个:
import {
HttpClient,
HttpHeaders,
HttpResponse,
HTTP_INTERCEPTORS
} from '@angular/common/http';
import {
HttpClientTestingModule,
HttpTestingController
} from '@angular/common/http/testing';
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
import { errorResponse } from './../../../shared/models/errorResponse.model';
import { WafErrorInterceptor } from './waf-error.service';
describe('WafErrorInterceptor', () => {
let httpMock: HttpTestingController;
let httpClient: HttpClient;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: WafErrorInterceptor,
multi: true
}
]
});
httpMock = TestBed.get(HttpTestingController);
httpClient = TestBed.get(HttpClient);
});
afterEach(() => {
httpMock.verify();
});
it('intercept: when no error, then subscribe returns successfully', () => {
const testData: string = 'test';
httpClient.get<string>('https://secure.testurl.com/success').subscribe(
(data) => expect(data).toBeTruthy(),
(error: errorResponse) => {
console.log(error);
fail('error should not have been called');
}
);
tick();
let req = httpMock.expectOne('https://secure.testurl.com/success');
tick();
let httpHeaders = new HttpHeaders();
httpHeaders.set('content-type', 'application/json');
const expectedResponse = new HttpResponse<string>({
status: 200,
statusText: 'OK',
body: testData,
headers: httpHeaders
});
//req.flush(expectedResponse);
req.event(expectedResponse);
});
});
我试过刷新,我只是发回数据,我发回数据和 headers/status。我发回 httpresponse 等的地方。每次,当它进入拦截器时,拦截器都看不到 httpresponse 类型的响应,并且 console.log 总是 returns false。
我什至刚刚创建了一个单元测试,其中单元测试发送了一个模拟对象......即使这样也有同样的问题。
想法?
更新:
所以下面的答案适用于地图,但在测试 catchErrorInterceptor 时仍然存在问题。该代码适用于我的网站。我们的 API returns 是一个对象,其中错误包含错误数组。所以我们抓住第一个并使用它。
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
catchError((error: HttpErrorResponse) => {
let statusCode = httpStatusCodes.CONFLICT;
let errorMessage = '';
let errorCode = '99999';
statusCode =
error.status === 0
? httpStatusCodes.INTERNAL_SERVER_ERROR
: error.status;
if (error.error.errors && error.error.errors.length > 0) {
errorCode = error.error.errors[0].code;
errorMessage = error.error.errors[0].description;
} else {
errorMessage = error.message;
}
const throwErrorResponse = new errorResponse(
statusCode,
errorCode,
errorMessage
);
return throwError(throwErrorResponse);
})
);
}
}
这是其中一项测试:
it('intercept: when delibarate 409, then error returned', (done) => {
httpClient
.get<string>('https://secure.go2bank.com/error')
.pipe(skip(1))
.subscribe(
(data) => fail('should have failed with the 404 error'),
(error: errorResponse) => {
expect(error).toBeTruthy(); // check if executed
expect(error.httpStatusCodes).toBe(
httpStatusCodes.CONFLICT
);
expect(error.errorCode).toBe('1007');
expect(error.errorMessage).toBe(
'Validation error Authorization'
);
done();
}
);
const errorInitEvent: ErrorEventInit = {
message: null,
error: {
errors: [
{
code: '1007',
description: 'Validation error Authorization.',
message: null,
link: null,
additionalinfo: null
}
]
}),
lineno: null,
colno: null,
filename: null
};
let error = new ErrorEvent('ERROR', errorInitEvent);
httpMock.expectOne('https://secure.go2bank.com/error').error(error, {
status: httpStatusCodes.CONFLICT,
statusText: 'Conflict',
headers: new HttpHeaders().set('content-type', 'application/json')
});
});
这个测试的结果总是 99999 而不是 1007。所以我得到了错误,它正在捕获错误。但是当我查看它时,error.error 是 ErrorEvent(isTrusted: //"),而且看起来我在 Error.
中没有错误数组
这里的主要问题是,如果 HttpClientTestingModule
Angular 使用 HttpClientTestingBackend
class 而不是 HttpXhrBackend 来模拟实际的 http 请求。
但是它们的实现有一个很大的不同:
HttpClientTestingBackend
总是发送 { type: HttpEventType.Sent }
事件,而 HttpXhrBackend 通常发送 HttpResponse
事件。
这意味着事件 { type: HttpEventType.Sent }
是在 map
rxjs 运算符中处理的第一个事件,它将失败。
因此,您需要像这样过滤响应:
interceptor.ts
import {
HttpEventType,
...
} from '@angular/common/http';
...
map((event: HttpEvent<any>) => {
if (event.type === HttpEventType.Sent) { <---------- add this
return event;
}
if (
event instanceof HttpResponse &&
event.headers.has('content-type') &&
event.headers.get('content-type') === 'application/json'
) {
return event;
}
...
更新:或者在测试中使用 skip(1) rxjs 运算符会更好.
现在,让我们回到您的测试。
首先,您需要删除所有多余的 tick()
调用。
然后 flush
方法的签名略有不同。
flush(body, opts?: {
headers?: HttpHeaders | {
[name: string]: string | string[];
};
status?: number;
statusText?: string;
})
但你试图将所有内容都放入 body
。
那么,这是您的测试用例:
interceptor.spec.ts
it('intercept: when no error, then subscribe returns successfully', () => {
const testData: string = 'test';
httpClient.get<string>('https://secure.testurl.com/success').subscribe(
(data) => expect(data).toBeTruthy(),
(error: errorResponse) => {
console.log(error);
fail('error should not have been called');
}
);
const req = httpMock.expectOne('https://secure.testurl.com/success');
req.flush(testData, {
status: 200,
statusText: 'OK',
headers: new HttpHeaders().set('content-type', 'application/json')
});
});
如果在 SO 周围搜索答案,到目前为止,我尝试过的所有内容都会产生相同的缺失信息。
这是 运行ning Angular 10 与 Karma/Jasmine 的最新版本。
本质上,我有一个 HTTP 拦截器正在查看 return 对象的内容类型。如果是json,照常继续,如果是html...然后抛出错误。
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
HttpResponse
} from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { httpStatusCodes } from '../../../shared/enums/httpStatusCodes.enum';
import { errorResponse } from './../../../shared/models/errorResponse.model';
@Injectable()
export class WafErrorInterceptor implements HttpInterceptor {
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
map((event: HttpEvent<any>) => {
console.log(event instanceof HttpResponse);
if (
event instanceof HttpResponse &&
event.headers.has('content-type') &&
event.headers.get('content-type') === 'application/json'
) {
return event;
}
const throwErrorResponse = new errorResponse(
httpStatusCodes.WAF_ERROR,
'99999',
event instanceof HttpResponse
? event.body
: 'unknown error occurred'
);
throw throwErrorResponse;
})
);
}
}
然后在我的单元测试中我 运行 这个:
import {
HttpClient,
HttpHeaders,
HttpResponse,
HTTP_INTERCEPTORS
} from '@angular/common/http';
import {
HttpClientTestingModule,
HttpTestingController
} from '@angular/common/http/testing';
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
import { errorResponse } from './../../../shared/models/errorResponse.model';
import { WafErrorInterceptor } from './waf-error.service';
describe('WafErrorInterceptor', () => {
let httpMock: HttpTestingController;
let httpClient: HttpClient;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: WafErrorInterceptor,
multi: true
}
]
});
httpMock = TestBed.get(HttpTestingController);
httpClient = TestBed.get(HttpClient);
});
afterEach(() => {
httpMock.verify();
});
it('intercept: when no error, then subscribe returns successfully', () => {
const testData: string = 'test';
httpClient.get<string>('https://secure.testurl.com/success').subscribe(
(data) => expect(data).toBeTruthy(),
(error: errorResponse) => {
console.log(error);
fail('error should not have been called');
}
);
tick();
let req = httpMock.expectOne('https://secure.testurl.com/success');
tick();
let httpHeaders = new HttpHeaders();
httpHeaders.set('content-type', 'application/json');
const expectedResponse = new HttpResponse<string>({
status: 200,
statusText: 'OK',
body: testData,
headers: httpHeaders
});
//req.flush(expectedResponse);
req.event(expectedResponse);
});
});
我试过刷新,我只是发回数据,我发回数据和 headers/status。我发回 httpresponse 等的地方。每次,当它进入拦截器时,拦截器都看不到 httpresponse 类型的响应,并且 console.log 总是 returns false。
我什至刚刚创建了一个单元测试,其中单元测试发送了一个模拟对象......即使这样也有同样的问题。
想法?
更新: 所以下面的答案适用于地图,但在测试 catchErrorInterceptor 时仍然存在问题。该代码适用于我的网站。我们的 API returns 是一个对象,其中错误包含错误数组。所以我们抓住第一个并使用它。
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
catchError((error: HttpErrorResponse) => {
let statusCode = httpStatusCodes.CONFLICT;
let errorMessage = '';
let errorCode = '99999';
statusCode =
error.status === 0
? httpStatusCodes.INTERNAL_SERVER_ERROR
: error.status;
if (error.error.errors && error.error.errors.length > 0) {
errorCode = error.error.errors[0].code;
errorMessage = error.error.errors[0].description;
} else {
errorMessage = error.message;
}
const throwErrorResponse = new errorResponse(
statusCode,
errorCode,
errorMessage
);
return throwError(throwErrorResponse);
})
);
}
}
这是其中一项测试:
it('intercept: when delibarate 409, then error returned', (done) => {
httpClient
.get<string>('https://secure.go2bank.com/error')
.pipe(skip(1))
.subscribe(
(data) => fail('should have failed with the 404 error'),
(error: errorResponse) => {
expect(error).toBeTruthy(); // check if executed
expect(error.httpStatusCodes).toBe(
httpStatusCodes.CONFLICT
);
expect(error.errorCode).toBe('1007');
expect(error.errorMessage).toBe(
'Validation error Authorization'
);
done();
}
);
const errorInitEvent: ErrorEventInit = {
message: null,
error: {
errors: [
{
code: '1007',
description: 'Validation error Authorization.',
message: null,
link: null,
additionalinfo: null
}
]
}),
lineno: null,
colno: null,
filename: null
};
let error = new ErrorEvent('ERROR', errorInitEvent);
httpMock.expectOne('https://secure.go2bank.com/error').error(error, {
status: httpStatusCodes.CONFLICT,
statusText: 'Conflict',
headers: new HttpHeaders().set('content-type', 'application/json')
});
});
这个测试的结果总是 99999 而不是 1007。所以我得到了错误,它正在捕获错误。但是当我查看它时,error.error 是 ErrorEvent(isTrusted: //"),而且看起来我在 Error.
中没有错误数组这里的主要问题是,如果 HttpClientTestingModule
Angular 使用 HttpClientTestingBackend
class 而不是 HttpXhrBackend 来模拟实际的 http 请求。
但是它们的实现有一个很大的不同:
HttpClientTestingBackend
总是发送 { type: HttpEventType.Sent }
事件,而 HttpXhrBackend 通常发送 HttpResponse
事件。
这意味着事件 { type: HttpEventType.Sent }
是在 map
rxjs 运算符中处理的第一个事件,它将失败。
因此,您需要像这样过滤响应:
interceptor.ts
import {
HttpEventType,
...
} from '@angular/common/http';
...
map((event: HttpEvent<any>) => {
if (event.type === HttpEventType.Sent) { <---------- add this
return event;
}
if (
event instanceof HttpResponse &&
event.headers.has('content-type') &&
event.headers.get('content-type') === 'application/json'
) {
return event;
}
...
更新:或者在测试中使用 skip(1) rxjs 运算符会更好.
现在,让我们回到您的测试。
首先,您需要删除所有多余的 tick()
调用。
然后 flush
方法的签名略有不同。
flush(body, opts?: {
headers?: HttpHeaders | {
[name: string]: string | string[];
};
status?: number;
statusText?: string;
})
但你试图将所有内容都放入 body
。
那么,这是您的测试用例:
interceptor.spec.ts
it('intercept: when no error, then subscribe returns successfully', () => {
const testData: string = 'test';
httpClient.get<string>('https://secure.testurl.com/success').subscribe(
(data) => expect(data).toBeTruthy(),
(error: errorResponse) => {
console.log(error);
fail('error should not have been called');
}
);
const req = httpMock.expectOne('https://secure.testurl.com/success');
req.flush(testData, {
status: 200,
statusText: 'OK',
headers: new HttpHeaders().set('content-type', 'application/json')
});
});