开发者问题收集

如何使用 React 测试库测试输入?

2022-06-17
19451

我正在尝试通过 React Testing Library 测试 Search 组件的 input 值。

Search 组件接收两个 props: handleChangeinput valuetitle

我的目标是编写测试,以便最初输入值为空,并且当用户输入某些内容时,测试可以验证监听事件值是否正确,但它始终获取初始值。

我试图在输入之前清除输入,但没有成功。

错误为:

 Expected the element to have value:
                       // indicates as empty string which I expect
                    
    Received:
      test            // it is still the default value: `test`, UserEvent.clear didn't clear

这里有两个测试,首先检查输入最初是否为空,这可以正常工作,第二个测试用于监听输入。

import { fireEvent, screen, waitFor } from "@testing-library/react";
import Search from "../Search";
import { render } from "../../../test-utils/test-utils";
import "@testing-library/jest-dom";
import userEvent from "@testing-library/user-event";

const mockedOnChange = jest.fn();

describe("input should be filled with title", () => {
  test("input should be empty initially", async () => {
    render(<Search title="" onChangeHadler={mockedOnChange} />);
    const input = screen.getByRole("textbox", { name: /search/i });
    expect(input).toHaveValue("");
  });
   // renderComponent wrapps the `Search` component
  const renderComponent = (searchInputValue: string) => {
    const view = render(
      <Search title={searchInputValue} onChangeHadler={mockedOnChange} />
    );
    return view;
  };

  test("input should update by typed text", async () => {
    renderComponent("test");
    const input = await screen.findByRole("textbox", { name: /search/i });
    await waitFor(() => expect(input).toHaveValue("test"));
    userEvent.clear(input);
    expect(input).toHaveValue(""); // error appears here indicating that value was not cleared

    // userEvent.type(input, "su"); 
    // await waitFor(() => expect(input).toHaveValue("su"));
  });
});
2个回答

您测试的组件是一个受控组件:它从父组件接收(props)其值,并使用函数引用通知父组件用户已更改输入值。通常,此函数将更新父状态,该状态将传递给子组件并用作输入值。

在测试中,您只渲染子组件,并模拟回调函数。因此,您不能指望在测试中触发用户交互时输入值会发生变化。您只能测试 title props 是否用作输入的值,以及触发用户交互时是否正确调用回调函数(在您的测试中模拟)。但是,如果不渲染父组件,您就无法测试“完整场景”。

test("input value is the title props", async () => {
    renderComponent("test");
    const input = await screen.findByRole("textbox", { name: /search/i });
    await waitFor(() => expect(input).toHaveValue("test"));
});

test("callback function is called on user interactions", async () => {
    renderComponent("test");
    const input = await screen.findByRole("textbox", { name: /search/i });
    userEvent.type(input, "new value");
    expect(mockedOnChange).toHaveBeenCalledWith("new value");
});
Florian Motteau
2022-06-17

正如@FlorianMotteau所提到的,如果您想在一系列事件(指针输入、键盘输入等)上测试受控组件(具有获取和更新其值的属性的组件),则应测试控制其值的祖先组件,即控制组件。如果控制组件不存在或过于复杂,您可以创建一个假的组件用于测试目的,如下所示:

import { useState } from 'react';
import { render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ControlledComponent } from './ControlledComponent'

const ControllingComponent = ({
  defaultValue,
  mockOnChange,
  ...rest
} => {
  const [value, setValue] = useState(defaultValue);
  mockOnChange.mockImplementation(v => setValue(v));
  return (
    <ControlledComponent value={value} onChange={mockOnChange} {...rest} />
  );
};

test('should call onChange on keyboard input', async () => {
  const user = userEvent.setup();
  const defaultValue = '';
  const mockOnChange = jest.fn();
  render(
    <ControllingComponent
      defaultValue={defaultValue}
      mockOnChange={mockOnChange}
    />,
  );
  await user.keyboard('abc');
  await waitFor(() => expect(mockOnChange).toHaveBeenCalledTimes(3));
  await waitFor(() => expect(mockOnChange).toHaveBeenNthCalledWith(1, 'a'));
  await waitFor(() => expect(mockOnChange).toHaveBeenNthCalledWith(2, 'ab'));
  await waitFor(() => expect(mockOnChange).toHaveBeenNthCalledWith(3, 'abc'));
});
Géry Ogam
2024-01-19