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

如何用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');
  });
});

关键说明

  • 通过执行mocksetExample传入的回调,让回调内的状态更新代码实际运行,从而被覆盖率工具统计到
  • mockvalidate函数保证异步流程稳定完成,避免真实验证逻辑干扰测试
  • 额外的draft状态验证可以确保状态更新逻辑符合预期,不只是覆盖代码,还能验证正确性

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 07:47:05