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

如何为InputSelect组件实现100%测试覆盖率?

实现InputSelect组件100%测试覆盖率的问题

问题说明

我在项目中用@storybook/testing-library和@storybook/jest测试InputSelect组件,测试用例全部通过,但代码覆盖率没到100%——第32行的(({ options, testId, ...rest }, ref) =>未被覆盖。已经测试了空选项、选项渲染、选项选择场景,还是没解决。

组件代码

export const InputSelect: FC<InputSelectProps> = forwardRef<
  InputSelectProps,
  HTMLSelectElement
>(({ options, testId, ...rest }, ref) =>
  isEmptyNode(options) ? null : (
    <Select data-testid={testId} {...rest} ref={ref}>
      {options?.map((o) => (
        <option key={o.value} value={o.value}>
          {o.children || o.value}
        </option>
      ))}
    </Select>
  )
);

现有测试用例

export const Default: Story = {
  parameters: {
    controls: {
      exclude: ['value', 'rootProps', 'testId', 'aria-errormessage'],
    },
  },
  args: {
    options: [
      { value: 'option1', children: 'Select1' },
      { value: 'option2', children: 'Select2' },
      { value: 'option3', children: 'Select3' },
      { value: 'option4', children: 'Select4' },
      { value: 'option5', children: 'Select5' },
    ],
  },
  render: (args) => (
    <>
      <InputSelect {...args} />
    </>
  ),
};

export const Tests: Story = {
  parameters: {
    controls: {
      exclude: ['value', 'rootProps', 'testId', 'aria-errormessage'],
    },
  },

  args: {
    ...Default.args,
  },

  render: (args) => (
    <>
      <InputSelect {...args} testId={'with-options'} />
      <InputSelect {...args} options={[]} testId={'without-options'} />
    </>
  ),

  play: async ({ args, canvasElement, step }) => {
    const canvas = within(canvasElement);

    const selectElement = canvas.queryByTestId(
      'with-options'
    ) as HTMLSelectElement;

    await step('The component only renders if it has options', async () => {
      await expect(selectElement).toBeTruthy();
      await expect(canvas.queryByTestId('without-options')).toBeFalsy();
      const renderedOptions = within(selectElement).queryAllByRole('option');
      expect(renderedOptions).toHaveLength(args.options?.length);
    });

    await step('select options correctly', async () => {
      userEvent.selectOptions(selectElement, 'Select1');
      expect(selectElement.value).toBe('option1');
      userEvent.selectOptions(selectElement, 'Select2');
      expect(selectElement.value).toBe('option2');
      userEvent.selectOptions(selectElement, 'Select3');
      expect(selectElement.value).toBe('option3');
      userEvent.selectOptions(selectElement, 'Select4');
      expect(selectElement.value).toBe('option4');
      userEvent.selectOptions(selectElement, 'Select5');
      expect(selectElement.value).toBe('option5');
    });
  },
};

解决方案

未覆盖的代码本质是三个场景没测到:ref参数的传递、options为null/undefined的情况、...rest额外props的传递。

1. 补充关键场景测试

修改Tests story,添加未覆盖场景的实例和测试步骤:

export const Tests: Story = {
  parameters: {
    controls: {
      exclude: ['value', 'rootProps', 'testId', 'aria-errormessage'],
    },
  },

  args: {
    ...Default.args,
    disabled: true, // 新增额外props用于测试
  },

  render: (args) => {
    const testRef = React.createRef<HTMLSelectElement>();
    return (
      <>
        <InputSelect {...args} testId={'with-options'} />
        <InputSelect {...args} options={[]} testId={'without-options'} />
        <InputSelect {...args} ref={testRef} testId={'with-ref'} /> {/* 测试ref传递 */}
        <InputSelect {...args} options={null} testId={'options-null'} /> {/* 测试options为null */}
        <InputSelect {...args} options={undefined} testId={'options-undefined'} /> {/* 测试options为undefined */}
      </>
    );
  },

  play: async ({ args, canvasElement, step }) => {
    const canvas = within(canvasElement);

    const selectElement = canvas.queryByTestId(
      'with-options'
    ) as HTMLSelectElement;

    await step('The component only renders if it has options', async () => {
      await expect(selectElement).toBeTruthy();
      await expect(canvas.queryByTestId('without-options')).toBeFalsy();
      const renderedOptions = within(selectElement).queryAllByRole('option');
      expect(renderedOptions).toHaveLength(args.options?.length);
    });

    await step('select options correctly', async () => {
      userEvent.selectOptions(selectElement, 'Select1');
      expect(selectElement.value).toBe('option1');
      userEvent.selectOptions(selectElement, 'Select2');
      expect(selectElement.value).toBe('option2');
      userEvent.selectOptions(selectElement, 'Select3');
      expect(selectElement.value).toBe('option3');
      userEvent.selectOptions(selectElement, 'Select4');
      expect(selectElement.value).toBe('option4');
      userEvent.selectOptions(selectElement, 'Select5');
      expect(selectElement.value).toBe('option5');
    });

    // 新增测试步骤:验证ref绑定
    await step('Test ref is correctly bound', async () => {
      const refElement = canvas.queryByTestId('with-ref') as HTMLSelectElement;
      expect(refElement.tagName).toBe('SELECT');
    });

    // 新增测试步骤:处理null/undefined的options
    await step('Handle null/undefined options correctly', async () => {
      expect(canvas.queryByTestId('options-null')).toBeFalsy();
      expect(canvas.queryByTestId('options-undefined')).toBeFalsy();
    });

    // 新增测试步骤:验证额外props传递
    await step('Pass extra props correctly', async () => {
      expect(selectElement.disabled).toBe(true);
    });
  },
};

完成以上修改后,就能覆盖所有未命中的代码分支,达到100%测试覆盖率。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 09:08:09