如何测试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-eventv14版本,建议先调用userEvent.setup()初始化实例再做操作,避免偶现的异步执行异常 - 不要直接断言selector的调用次数,Redux selector在组件渲染、状态更新时可能多次触发,断言调用行为比断言次数更可靠
- 要完全覆盖两个函数的所有分支,可以分别模拟不同的
prevCapacity、prevLocation、filteredTables返回值,覆盖if/else两种执行路径
内容的提问来源于stack exchange,提问作者Michelangelo
相关产品推荐
相关产品推荐

