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

如何为包含useId的React组件编写稳定的快照测试?

解决React useId导致快照测试不稳定的问题

问题背景

我用useId实现了一个React表单组件:

function Form() {
  const id = useId();

  return (
    <div>
      <label htmlFor={id}>name</label>
      <input id={id} />
    </div>
  );
}

使用Testing Library做快照测试时,生成的快照里ID为:r1::

import { render } from '@testing-library/react';

describe('Snapshot', () => {
  test('default', () => {
    const { asFragment } = render(<Form />);
    expect(asFragment()).toMatchSnapshot();
  });
});

快照内容:

exports[`snapshot 1`] = `
<DocumentFragment>
  <div>
    <label
      for=":r1:"
    >
      name
    </label>
    <input
      id=":r1:"
    />
  </div>
</DocumentFragment>
`;

但新增测试后,ID会变成:r2:、:r3:这类动态值,直接导致快照测试失败。我已经通过Mock useId的方式解决了问题:

let id = 0;

beforeEach(() => {
  id = 0;
});

const generateId = () => ++id;

jest.mock('react', () => ({
  ...jest.requireActual('react'),
  useId: generateId,
}));

这个方案能让每个测试生成固定序列的ID,但想知道有没有更优的解决方案?

更优解决方案

1. 简化useId的Mock逻辑

不需要自己维护计数器,直接用Jest的Mock函数返回固定值即可:

import { render } from '@testing-library/react';
import { jest } from '@jest/globals';

// 全局Mock useId,所有测试返回固定ID
jest.mock('react', () => ({
  ...jest.requireActual('react'),
  useId: jest.fn(() => 'fixed-input-id'),
}));

describe('Snapshot', () => {
  test('default', () => {
    const { asFragment } = render(<Form />);
    expect(asFragment()).toMatchSnapshot();
  });
});

如果需要多个不同的固定ID,可以用mockReturnValueOnce指定序列:

jest.mock('react', () => ({
  ...jest.requireActual('react'),
  useId: jest.fn().mockReturnValueOnce('id-1').mockReturnValueOnce('id-2'),
}));

2. 快照中忽略ID属性

如果ID不是测试的核心关注点,可以在生成快照时直接忽略id和for属性:

test('default', () => {
  const { asFragment } = render(<Form />);
  expect(asFragment()).toMatchSnapshot({
    print: (val) => {
      // 移除label的for属性和input的id属性
      if (val.type === 'label') delete val.props.for;
      if (val.type === 'input') delete val.props.id;
      return require('jest-snapshot').print(val);
    },
  });
});

这种方式不需要修改组件或Mock函数,适合只关注组件结构、不关心ID绑定的场景。

3. 给组件传入自定义ID(推荐)

修改组件,让它接受一个可选的id props,生产环境用useId兜底,测试时传入固定ID:

function Form({ id: propId }) {
  const id = propId ?? useId();

  return (
    <div>
      <label htmlFor={id}>name</label>
      <input id={id} />
    </div>
  );
}

测试代码:

test('default', () => {
  const { asFragment } = render(<Form id="test-name-input" />);
  expect(asFragment()).toMatchSnapshot();
});

这种方式最符合React组件的设计原则,既保留了生产环境useId的唯一性,又让测试完全可控,同时还提升了组件的可定制性。

方案对比

  • Mock useId:无需修改组件,但要注意Mock的作用域,多组件使用时可能需要调整逻辑。
  • 忽略快照属性:操作简单,但会丢失DOM的ID绑定信息,不适合需要验证ID关联的测试。
  • 传入自定义ID:最推荐,逻辑清晰,兼顾生产和测试需求,还增强了组件的灵活性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 18:32:43