如何用Jest测试useImmer React Hook的setter及回调逻辑?
问题:如何覆盖useImmer状态更新回调的测试覆盖率
背景代码
父组件中使用useImmer定义状态:
const [example, setExample] = useImmer({ submitterId: { value: EMPTY_STRING, label: 'Submitter ID', disabled: false, status: null, schema: submitterIdSchema }, submitterName: { value: EMPTY_STRING, label: 'Submitter Name', disabled: false, status: null, schema: submitterNameSchema } });
子组件ChildComponent接收状态和更新函数,核心逻辑:
export function ChildComponent({ example, setExample, }) { const handleInputChange = async (e, fieldName) => { const valueToUpdateWith = e.target.value; const schema = example[fieldName].yupSchema; const status = await validate(valueToUpdateWith, schema); setExample((draft) => { draft[fieldName].status = status; draft[fieldName].value = valueToUpdateWith; }); }; return ( <div id="exampleContainer"> <ConnectedFieldGenerator // 遍历example的key生成输入框组 fieldKeys={Object.keys(example)} handleInputChange={handleInputChange} /> </div> ); }
当前测试的问题
现有测试代码:
const props = { example: EXAMPLE_INITIAL_FIELD_STATE, setExample: jest.fn(), }; describe('Search Table Renders', () => { it('input change of example', async () => { const { container } = render(<ChildComponent{...props} />); await enterInputValue(container, '#submitterId-input', 'Submitter ID'); expect(props.setBasicSearchState).toHaveBeenCalled(); }); });
覆盖率报告显示setExample回调内的状态更新代码未被覆盖,原因:
jest.fn()mock的setExample只会记录调用行为,不会执行传入的draft回调函数- 异步的
validate函数未被mock,测试执行流程可能未走到状态更新步骤 - 测试中的断言写错了,应该断言
setExample而非setBasicSearchState
修改后的测试方案
步骤1:mock validate函数
确保异步验证逻辑返回确定值,避免依赖真实schema验证:
// 假设validate是单独导出的模块,先mock它 jest.mock('../path/to/validate', () => ({ __esModule: true, default: jest.fn().mockResolvedValue('valid') // 模拟验证通过的状态 }));
步骤2:让mock的setExample执行回调函数
修改setExample的mock实现,手动调用传入的draft回调:
const props = { example: EXAMPLE_INITIAL_FIELD_STATE, setExample: jest.fn((callback) => { // 执行传入的draft回调,传入一个模拟的draft对象 const draft = { ...props.example }; callback(draft); }), };
步骤3:修正断言并补充验证
确保断言正确,且可以验证回调内的状态更新逻辑:
describe('Search Table Renders', () => { it('input change updates example state correctly', async () => { const { container } = render(<ChildComponent{...props} />); const testValue = 'Submitter ID'; await enterInputValue(container, '#submitterId-input', testValue); // 断言setExample被调用 expect(props.setExample).toHaveBeenCalled(); // 验证回调内的状态更新逻辑 const updateCallback = props.setExample.mock.calls[0][0]; const draft = { ...EXAMPLE_INITIAL_FIELD_STATE }; updateCallback(draft); expect(draft.submitterId.value).toBe(testValue); expect(draft.submitterId.status).toBe('valid'); }); });
关键说明
- 通过执行mock
setExample传入的回调,让回调内的状态更新代码实际运行,从而被覆盖率工具统计到 - mock
validate函数保证异步流程稳定完成,避免真实验证逻辑干扰测试 - 额外的draft状态验证可以确保状态更新逻辑符合预期,不只是覆盖代码,还能验证正确性
内容的提问来源于stack exchange,提问作者VoodooJellyfish
相关产品推荐
相关产品推荐

