React 测试库适用于 HTML 个元素,但不适用于 React 组件

React Testing Library works on HTML elements but not with React components

基本上,我正在尝试测试具有 select.

的组件

尝试测试组件时,测试失败,返回的是默认值而不是更改后的值。

但是当我使用渲染组件的 HTML 时(来自 screen.debug())它起作用了。

组件:

export function SelectFile({
  fileList,
  handleChange,
  selected,
}) {
  return (
    <select
      className="bg-slate-600 rounded w-auto"
      onChange={onChange}
      value={selected}
    >
      <option value="">Select an option</option>
      <TodayOptions />
      <AllOptions />
    </select>
  );

  function AllOptions() {
    return (
      <>
        {Object.entries(groups).map(([key, value]) => {
          return (
            <optgroup key={key} label={key.toLocaleUpperCase()}>
              {[...value].sort(sortByDateFromLogs).map((item) => (
                <option key={item} value={item}>
                  {item}
                </option>
              ))}
            </optgroup>
          );
        })}
      </>
    );
  }

  function TodayOptions() {
    const todayFiles = Object.values(groups)
      .map((group) => {
        const today = new Date().toLocaleDateString().replace(/\//g, '-');
        return group.filter((file) => file.includes(today));
      })
      .flat();

    if (todayFiles.length === 0) {
      return null;
    }

    return (
      <optgroup label="Today">
        {todayFiles.map((item) => (
          <option key={item}>{item}</option>
        ))}
      </optgroup>
    );
  }
}

原测:

 it('should change option', () => {
    render(
      <SelectFile
        fileList={fileList}
        handleChange={handleChange}
        selected=""
      />,
    );

    const selectElement = screen.getByDisplayValue('Select an option');
    const allOptions = screen.getAllByRole('option');

    const optionSelected = fileList.adonis[1];

    expect(selectElement).toHaveValue('');

    act(() => {
      userEvent.selectOptions(selectElement, optionSelected);
    });

    expect(handleChange).toHaveBeenCalledTimes(1);
    expect(selectElement).toHaveValue(optionSelected); // returns "" (default value)
    expect((allOptions[0] as HTMLOptionElement).selected).toBe(false);
    expect((allOptions[1] as HTMLOptionElement).selected).toBe(true);
    expect((allOptions[2] as HTMLOptionElement).selected).toBe(false);
    expect((allOptions[3] as HTMLOptionElement).selected).toBe(false);
    expect((allOptions[4] as HTMLOptionElement).selected).toBe(false);
  });

以及使用渲染后的修改后的测试 html:

 it('should change option', () => {
    render(
      <div>
        <div className="flex mr-10">
          <h3 className="text-lg font-bold mr-4">Select a file</h3>
          <select className="bg-slate-600 rounded w-auto">
            <option value="">Select an option</option>
            <optgroup label="ADONIS">
              <option value="adonis-03-02-2022.json">
                adonis-03-02-2022.json
              </option>
              <option value="adonis-02-02-2022.json">
                adonis-02-02-2022.json
              </option>
            </optgroup>
            <optgroup label="ERRORS">
              <option value="http_errors-03-03-2022.log">
                http_errors-03-03-2022.log
              </option>
              <option value="http_errors-04-02-2022.log">
                http_errors-04-02-2022.log
              </option>
            </optgroup>
          </select>
        </div>
      </div>,
    );

    const selectElement = screen.getByDisplayValue('Select an option');
    const allOptions = screen.getAllByRole('option');

    const optionSelected = fileList.adonis[1];

    expect(selectElement).toHaveValue('');

    act(() => {
      userEvent.selectOptions(selectElement, optionSelected);
    });

    expect(selectElement).toHaveValue(optionSelected); // this returns the optionSelected value
    expect((allOptions[0] as HTMLOptionElement).selected).toBe(false);
    expect((allOptions[1] as HTMLOptionElement).selected).toBe(true);
    expect((allOptions[2] as HTMLOptionElement).selected).toBe(false);
    expect((allOptions[3] as HTMLOptionElement).selected).toBe(false);
    expect((allOptions[4] as HTMLOptionElement).selected).toBe(false);
  });

考虑到它适用于修改后的测试,我无法解释为什么它不适用于原始测试。 我认为这是由于 optgroup,但似乎并非如此,所以现在我不知道为什么。


编辑:测试最终版:

  it('should change option', () => {
    const mockHandleChange = handleChange.mockImplementation(
      (cb) => (e) => cb(e.target.value),
    );

    render(
      <SelectWrapper fileList={fileList} handleChange={mockHandleChange} />,
    );

    const selectElement = screen.getByDisplayValue('Select an option');

    const optionSelected = fileList.adonis[1];

    expect(selectElement).toHaveValue('');

    act(() => {
      userEvent.selectOptions(selectElement, optionSelected);
    });

    expect(handleChange).toHaveBeenCalledTimes(2); // 1 for cb wrapper, 1 for select
    expect(selectElement).toHaveValue(optionSelected);
  });
});

const SelectWrapper = ({ handleChange, fileList }) => {
  const [selected, setSelected] = useState('');

  const mockHandleChange = handleChange(setSelected);

  return (
    <SelectFile
      fileList={fileList}
      handleChange={mockHandleChange}
      selected={selected}
    />
  );
};

我创建了一个包装器,使它像您在另一个组件中使用的那样,包装了模拟函数,现在它更改了值,您可以访问模拟。

因为在您的测试中您只呈现 Select(这是一个受控组件:它从其父级接收当前值和 onChange 回调),具有固定的 selected 属性,当你触发 select 上的更改事件时,你不能指望 selected 选项会更改。您只能期望 onChange 回调已被调用(就像您所做的那样)。

对于这种组件,您需要测试是否尊重 selected 道具(selected 选项是好的),并且当用户调用时调用提供的回调选择一个新选项(你已经完成了这部分)。

您需要使用现有选项添加测试作为 selected 道具(不是空字符串),然后检查 selected 选项是否正确。我建议你使用 https://github.com/testing-library/jest-dom#tohavevalue from https://github.com/testing-library/jest-dom.