Angular单元测试Karma报错:预期1个匹配请求却找到2个求助
Angular单元测试HTTP请求报错问题解析
错误信息
Error: Expected one matching request for criteria "Match URL: https://reqres.in/api/users?page=2", found 2 requests. at HttpClientTestingBackend.expectOne (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/common/fesm2015/http/testing.mjs:279:19) at UserContext.<anonymous> (http://localhost:9876/_karma_webpack_/webpack:/src/app/admin/employee/employee.component.spec.ts:73:26) at _ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/fesm2015/zone.js:372:1)
相关代码
Employee.component.ts
import { Component,OnInit } from '@angular/core'; import { EmployeeService } from 'src/app/Service/employee.service'; @Component({ selector: 'app-employee', templateUrl: './employee.component.html', styleUrls: ['./employee.component.css'] }) export class EmployeeComponent implements OnInit { public employeeDetails:any[] = []; public newEmployee:any = { name:"", job:"" } constructor(public employee:EmployeeService) { } ngOnInit(): void { this.getEmployeeList(); } getEmployeeList(){ this.employee.getEmployeeList().subscribe( data=>{ this.employeeDetails = data.data; console.log(data); }, error=>{ console.log(error); } ) } AddNewEmployee(){ this.employeeDetails.push(this.newEmployee); } DeleteEmployee(id:Number){ this.employee.deleteEmp(id).subscribe( data=>{ console.log(data); } ) } }
Employee.component.spec.ts
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { EmployeeComponent } from './employee.component'; import { EmployeeService } from 'src/app/Service/employee.service'; describe('EmployeeComponent', () => { let component: EmployeeComponent; let fixture: ComponentFixture<EmployeeComponent>; let service: EmployeeService; let httpMock: HttpTestingController; const emp = { data: [ { "id": 7, "email": "michael.lawson@reqres.in", "first_name": "Michael", "last_name": "Lawson", "avatar": "https://reqres.in/img/faces/7-image.jpg" }, { "id": 8, "email": "lindsay.ferguson@reqres.in", "first_name": "Lindsay", "last_name": "Ferguson", "avatar": "https://reqres.in/img/faces/8-image.jpg" }, { "id": 9, "email": "tobias.funke@reqres.in", "first_name": "Tobias", "last_name": "Funke", "avatar": "https://reqres.in/img/faces/9-image.jpg" } ] } beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], declarations: [EmployeeComponent], providers: [EmployeeService] }); fixture = TestBed.createComponent(EmployeeComponent); component = fixture.componentInstance; service = TestBed.inject(EmployeeService); httpMock = TestBed.inject(HttpTestingController); fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); it('should call ngonInit method', () => { spyOn(component, 'ngOnInit').and.callThrough(); component.ngOnInit(); expect(component.ngOnInit).toHaveBeenCalled(); }); it('should return list of employees', () => { service.getEmployeeList().subscribe((data) => { expect(data).toEqual(emp.data); }) const req = httpMock.expectOne('https://reqres.in/api/users?page=2'); expect(req.request.method).toBe('GET'); req.flush(emp.data); httpMock.verify(); }) });
错误含义与代码问题
错误含义
该错误表示:测试代码通过httpMock.expectOne()期望捕获1次指向https://reqres.in/api/users?page=2的HTTP请求,但实际检测到了2次完全相同的请求,不符合断言预期。
代码问题
问题出在测试初始化逻辑与测试用例的双重请求触发:
beforeEach钩子中调用了fixture.detectChanges(),触发组件生命周期执行ngOnInit,而ngOnInit内部调用getEmployeeList()发起了第一次HTTP请求;- 第三个测试用例
should return list of employees中,又主动调用service.getEmployeeList().subscribe()发起了第二次相同的HTTP请求; - 两次请求都匹配
expectOne的URL条件,导致断言失败。
另外测试中还有一个隐性问题:组件订阅getEmployeeList()时取的是data.data,说明服务返回的是包含data字段的对象(和你定义的emp结构一致),但测试里断言expect(data).toEqual(emp.data)、并flush(emp.data),这会导致断言不匹配。
修复方案
方案1:调整组件初始化时机
移除beforeEach中的fixture.detectChanges(),只在需要触发组件初始化的测试用例中调用:
beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], declarations: [EmployeeComponent], providers: [EmployeeService] }); fixture = TestBed.createComponent(EmployeeComponent); component = fixture.componentInstance; service = TestBed.inject(EmployeeService); httpMock = TestBed.inject(HttpTestingController); // 移除此处的 fixture.detectChanges() }); it('should create', () => { fixture.detectChanges(); // 仅在需要的测试用例中触发组件初始化 expect(component).toBeTruthy(); }); it('should return list of employees', () => { service.getEmployeeList().subscribe((data) => { expect(data).toEqual(emp); // 修正断言,匹配完整的emp对象 }) const req = httpMock.expectOne('https://reqres.in/api/users?page=2'); expect(req.request.method).toBe('GET'); req.flush(emp); // 修正flush数据,传入完整的emp对象 httpMock.verify(); })
方案2:先处理初始化时的请求
如果需要保留beforeEach中的fixture.detectChanges(),可以在测试用例开始前先处理掉初始化触发的请求:
it('should return list of employees', () => { // 先处理组件初始化时发起的请求 const initReq = httpMock.expectOne('https://reqres.in/api/users?page=2'); initReq.flush(emp); // 再执行测试逻辑 service.getEmployeeList().subscribe((data) => { expect(data).toEqual(emp); }) const testReq = httpMock.expectOne('https://reqres.in/api/users?page=2'); expect(testReq.request.method).toBe('GET'); testReq.flush(emp); httpMock.verify(); })
内容的提问来源于stack exchange,提问作者Hitha
相关产品推荐
相关产品推荐

