Angular全局HTTP拦截器单元测试求助:POST请求自动添加XSCRF Token功能测试未通过
解决全局HTTP拦截器单元测试失败的问题
我帮你梳理下测试用例失败的核心问题,以及对应的修复方案:
首先,先修正业务代码里的小错误
你的optout.service.ts里getReponse()拼写错误,应该是getResponse(),否则调用时会直接报错:
// 修正后的OptoutService方法 getResponse(): Observable<optout> { return this.httpClient.get<any>(`https://thisIsGetTokenApi.com`).pipe( mergeMap(token => this.httpClient.get<optout>(`https://someurlenpoint2.com`)) ); } postResponse(data: optout): Observable<optout> { return this.httpClient.post<optout>('https://api-endpoint', data); }
测试用例的核心问题及修复
你的测试用例存在几个关键问题,导致断言始终失败:
1. 未处理异步流的时序问题
调用optoutService的方法都是异步操作,必须用fakeAsync和tick()等待所有异步操作完成,否则断言会在拦截器处理响应、存储Token前就执行。
2. 未正确模拟两个请求的响应
getResponse()通过mergeMap链式调用了两个接口,需要分别模拟这两个请求的响应——尤其是第一个获取Token的请求,要返回包含XCSRFTOKEN的响应体,这样拦截器才能正确存储Token。
3. 断言对象错误
你现在断言的是获取Token的请求头,但拦截器的逻辑是在POST请求时添加X-CSRF-TOKEN头,应该验证POST请求的头是否正确。
4. 未定义测试变量
测试里的getTokenApi变量未声明,需要明确对应到获取Token的接口地址。
修正后的完整测试代码
import { fakeAsync, TestBed, tick } from '@angular/core/testing'; import { GlobalHttpInterceptorService } from './global-http-interceptor.service'; import { RouterTestingModule } from '@angular/router/testing'; import { OptoutService } from './optout.service'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { optout } from '../model/optoutModel'; describe('GlobalHttpInterceptorService', () => { let service: GlobalHttpInterceptorService; let optoutService: OptoutService; let httpMock: HttpTestingController; // 定义测试用的API地址常量 const GET_TOKEN_API = 'https://thisIsGetTokenApi.com'; const GET_FORM_API = 'https://someurlenpoint2.com'; const POST_API = 'https://api-endpoint'; beforeEach(() => { TestBed.configureTestingModule({ imports: [RouterTestingModule, HttpClientTestingModule], providers: [ OptoutService, { provide: HTTP_INTERCEPTORS, useClass: GlobalHttpInterceptorService, multi: true } ] }); service = TestBed.inject(GlobalHttpInterceptorService); optoutService = TestBed.inject(OptoutService); httpMock = TestBed.inject(HttpTestingController); }); afterEach(() => { // 确保所有HTTP请求都被处理,避免内存泄漏 httpMock.verify(); }); it('should be created', () => { expect(service).toBeTruthy(); }); it('should add X-CSRF-TOKEN header to POST requests after fetching token', fakeAsync(() => { // 1. 定义模拟数据 const mockTokenResponse = { XCSRFTOKEN: 'test-csrf-token' }; const mockFormResponse: optout = { output: { somevar1: "somvalue", somevar2: false } }; const mockPostData: optout = { output: { somevar1: "somvalue2", somevar2: false } }; // 2. 调用getResponse触发Token获取 optoutService.getResponse().subscribe(res => { expect(res).toEqual(mockFormResponse); }); // 3. 模拟第一个获取Token的请求响应 const tokenReq = httpMock.expectOne(GET_TOKEN_API); tokenReq.flush(mockTokenResponse); // 4. 模拟第二个获取表单的请求响应 const formReq = httpMock.expectOne(GET_FORM_API); formReq.flush(mockFormResponse); // 等待异步流完成,确保拦截器已经存储了Token tick(); // 5. 调用postResponse发起POST请求 optoutService.postResponse(mockPostData).subscribe(res => { expect(res).toEqual(mockPostData); }); // 6. 捕获POST请求并验证请求头 const postReq = httpMock.expectOne(POST_API); expect(postReq.request.method).toBe('POST'); // 断言POST请求头包含正确的X-CSRF-TOKEN expect(postReq.request.headers.get('X-CSRF-TOKEN')).toBe('test-csrf-token'); // 模拟POST请求的响应 postReq.flush(mockPostData); // 等待所有异步操作完成 tick(); })); });
额外说明
afterEach里的httpMock.verify()用于确保所有发起的HTTP请求都被模拟响应,避免测试用例遗漏请求导致的问题。- 用
fakeAsync和tick()可以同步处理异步操作,确保拦截器有足够时间处理响应并存储Token,之后再验证POST请求的头。 - 明确每个请求的模拟顺序,严格对应
getResponse()里的链式调用流程。
内容的提问来源于stack exchange,提问作者blume0F
相关产品推荐
相关产品推荐

