You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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次完全相同的请求,不符合断言预期。

代码问题

问题出在测试初始化逻辑与测试用例的双重请求触发:

  1. beforeEach钩子中调用了fixture.detectChanges(),触发组件生命周期执行ngOnInit,而ngOnInit内部调用getEmployeeList()发起了第一次HTTP请求;
  2. 第三个测试用例should return list of employees中,又主动调用service.getEmployeeList().subscribe()发起了第二次相同的HTTP请求;
  3. 两次请求都匹配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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 16:52:16