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

如何测试React组件内部声明的handleChange、changeCapacity类自定义函数

问题根因

  • 所有waitFor和user.selectOptions操作都没有加await,异步操作还没执行完成测试就已经结束,绑定的onChange函数根本没触发,自然不会统计到覆盖率
  • beforeEach里把tablesSelectorSpy的返回值设为空数组,组件渲染时uniqueLocations也为空,不存在值为Patio的选项,选择Patio的操作本身就执行失败
  • 你默认mock的filteredTablesSelectorSpy返回空数组、prevCapacity返回0,就算触发了onChange也只会走else分支,没法覆盖函数全逻辑

修复后的测试代码

describe("TableFilter", () => {
  const filteredTablesSelectorSpy = jest.spyOn(
    actions,
    "filteredTablesSelector"
  );
  const tablesSelectorSpy = jest.spyOn(actions, "tablesSelector");
  const capacitySelectorSpy = jest.spyOn(actions, "capacitySelector");
  const locationSelectorSpy = jest.spyOn(actions, "locationSelector");
  const mockHandleTables = jest.fn();

  beforeEach(() => {
    jest.clearAllMocks();
    // 模拟真实的桌子数据,包含Patio位置的选项
    tablesSelectorSpy.mockReturnValue([
      { location: "Patio", capacity: 2 },
      { location: "Patio", capacity: 8 },
      { location: "Indoor", capacity: 4 }
    ]);
    filteredTablesSelectorSpy.mockReturnValue([]);
    capacitySelectorSpy.mockReturnValue(0);
    locationSelectorSpy.mockReturnValue("");
  });

  // 测试用例添加async关键字
  test("testing Capacity and Location Selects, should find the Table with the values selected", async () => {
    render(<TableFilter handleTables={mockHandleTables} />);

    const capacitySelect = screen.getByLabelText("form-select-capacity");
    const locationSelect = screen.getByLabelText("form-select-location");

    expect(capacitySelect).toBeInTheDocument();
    expect(locationSelect).toBeInTheDocument();

    // 异步操作添加await
    await user.selectOptions(
      capacitySelect,
      within(capacitySelect).getByRole("option", { name: "8" })
    );

    await user.selectOptions(
      locationSelect,
      within(locationSelect).getByRole("option", { name: "Patio" })
    );

    // 前面操作已经await完成,状态已更新,不需要多余的waitFor
    expect(screen.getByRole("option", { name: "8" }).selected).toBeTruthy();
    expect(screen.getByRole("option", { name: "Patio" }).selected).toBeTruthy();

    expect(filteredTablesSelectorSpy).toHaveBeenCalled();
    expect(tablesSelectorSpy).toHaveBeenCalled();
    expect(capacitySelectorSpy).toHaveBeenCalled();
    expect(locationSelectorSpy).toHaveBeenCalled();
  });

  // 新增用例覆盖if分支逻辑
  test("should trigger handleTables when no matched tables exist", async () => {
    locationSelectorSpy.mockReturnValue("Patio");
    filteredTablesSelectorSpy.mockReturnValue([{ location: "Patio", capacity: 2 }]);
    render(<TableFilter handleTables={mockHandleTables} />);

    // 选择超出现有容量的数值,触发无匹配结果的逻辑
    await user.selectOptions(
      screen.getByLabelText("form-select-capacity"),
      "9"
    );

    expect(mockHandleTables).toHaveBeenCalled();
  });
});

额外注意事项

  • 如果使用的是@testing-library/user-event v14版本,建议先调用userEvent.setup()初始化实例再做操作,避免偶现的异步执行异常
  • 不要直接断言selector的调用次数,Redux selector在组件渲染、状态更新时可能多次触发,断言调用行为比断言次数更可靠
  • 要完全覆盖两个函数的所有分支,可以分别模拟不同的prevCapacity、prevLocation、filteredTables返回值,覆盖if/else两种执行路径

内容的提问来源于stack exchange,提问作者Michelangelo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 05:24:05