如何使用react-testing-library测试react-select

use*_*814 6 integration-testing reactjs react-select react-testing-library

App.js

import React, { Component } from "react";
import Select from "react-select";

const SELECT_OPTIONS = ["FOO", "BAR"].map(e => {
  return { value: e, label: e };
});

class App extends Component {
  state = {
    selected: SELECT_OPTIONS[0].value
  };

  handleSelectChange = e => {
    this.setState({ selected: e.value });
  };

  render() {
    const { selected } = this.state;
    const value = { value: selected, label: selected };
    return (
      <div className="App">
        <div data-testid="select">
          <Select
            multi={false}
            value={value}
            options={SELECT_OPTIONS}
            onChange={this.handleSelectChange}
          />
        </div>
        <p data-testid="select-output">{selected}</p>
      </div>
    );
  }
}

export default App;
Run Code Online (Sandbox Code Playgroud)

App.test.js

import React from "react";
import {
  render,
  fireEvent,
  cleanup,
  waitForElement,
  getByText
} from "react-testing-library";
import App from "./App";

afterEach(cleanup);

const setup = () => {
  const utils = render(<App />);
  const selectOutput = utils.getByTestId("select-output");
  const selectInput = document.getElementById("react-select-2-input");
  return { selectOutput, selectInput };
};

test("it can change selected item", async () => {
  const { selectOutput, selectInput } = setup();
  getByText(selectOutput, "FOO");
  fireEvent.change(selectInput, { target: { value: "BAR" } });
  await waitForElement(() => getByText(selectOutput, "BAR"));
});
Run Code Online (Sandbox Code Playgroud)

这个最小的示例在浏览器中可以正常工作,但是测试失败。我认为没有调用onChange处理程序。如何在测试中触发onChange回调?查找fireEvent元素的首选方法是什么?谢谢

mom*_*omo 41

在我的项目中,我使用 react-testing-library 和 jest-dom。我遇到了同样的问题 - 经过一番调查,我找到了解决方案,基于线程:https : //github.com/airbnb/enzyme/issues/400

请注意,渲染的顶级函数以及各个步骤必须是异步的。

在这种情况下不需要使用焦点事件,它允许选择多个值。

此外,getSelectItem 内必须有异步回调。

const DOWN_ARROW = { keyCode: 40 };

it('renders and values can be filled then submitted', async () => {
  const {
    asFragment,
    getByLabelText,
    getByText,
  } = render(<MyComponent />);

  ( ... )

  // the function
  const getSelectItem = (getByLabelText, getByText) => async (selectLabel, itemText) => {
    fireEvent.keyDown(getByLabelText(selectLabel), DOWN_ARROW);
    await waitForElement(() => getByText(itemText));
    fireEvent.click(getByText(itemText));
  }

  // usage
  const selectItem = getSelectItem(getByLabelText, getByText);

  await selectItem('Label', 'Option');

  ( ... )

}
Run Code Online (Sandbox Code Playgroud)

  • 我个人更喜欢这个解决方案,而不是接受的答案,因为你可以保持原样。通过这种方式,您可以真正测试事物,就像用户测试它们一样。如果你模拟“react-select”,你甚至需要测试自己的模拟,这在某种程度上会适得其反。而且,如果你使用“react-select”提供的更复杂的属性,你的模拟也会变得更复杂,而且也难以维护恕我直言 (7认同)
  • 只是出色的解决方案。顺便说一下,waitforElement() 现在已被弃用。我这样做是这样的:`await screen.findByText(itemText);` (3认同)

Con*_*tin 15

最后,有一个库可以帮助我们解决这个问题:https : //testing-library.com/docs/ecosystem-react-select-event。适用于单选或多选:

@testing-library/react文档:

import React from 'react'
import Select from 'react-select'
import { render } from '@testing-library/react'
import selectEvent from 'react-select-event'

const { getByTestId, getByLabelText } = render(
  <form data-testid="form">
    <label htmlFor="food">Food</label>
    <Select options={OPTIONS} name="food" inputId="food" isMulti />
  </form>
)
expect(getByTestId('form')).toHaveFormValues({ food: '' }) // empty select

// select two values...
await selectEvent.select(getByLabelText('Food'), ['Strawberry', 'Mango'])
expect(getByTestId('form')).toHaveFormValues({ food: ['strawberry', 'mango'] })

// ...and add a third one
await selectEvent.select(getByLabelText('Food'), 'Chocolate')
expect(getByTestId('form')).toHaveFormValues({
  food: ['strawberry', 'mango', 'chocolate'],
})
Run Code Online (Sandbox Code Playgroud)

感谢https://github.com/romgain/react-select-event提供这么棒的包!


Ves*_*ide 11

与@momimomo 的回答类似,我写了一个小助手来从react-selectTypeScript 中选择一个选项。

帮助文件:

import { getByText, findByText, fireEvent } from '@testing-library/react';

const keyDownEvent = {
    key: 'ArrowDown',
};

export async function selectOption(container: HTMLElement, optionText: string) {
    const placeholder = getByText(container, 'Select...');
    fireEvent.keyDown(placeholder, keyDownEvent);
    await findByText(container, optionText);
    fireEvent.click(getByText(container, optionText));
}
Run Code Online (Sandbox Code Playgroud)

用法:

export const MyComponent: React.FunctionComponent = () => {
    return (
        <div data-testid="day-selector">
            <Select {...reactSelectOptions} />
        </div>
    );
};
Run Code Online (Sandbox Code Playgroud)
export const MyComponent: React.FunctionComponent = () => {
    return (
        <div data-testid="day-selector">
            <Select {...reactSelectOptions} />
        </div>
    );
};
Run Code Online (Sandbox Code Playgroud)


Gio*_*Gpx 10

这一定是关于RTL的最常见的问题:D

最好的策略是使用jest.mock(或测试框架中的等效方法)模拟选择并呈现HTML选择。

有关为什么这是最佳方法的更多信息,我也写了一些适用于这种情况的内容。OP询问了Material-UI中的选择,但是想法是一样的。

原始问题和我的答案:

因为您无法控制该UI。它在第3方模块中定义。

因此,您有两个选择:

您可以找出材质库创建的HTML,然后使用container.querySelector查找其元素并与之交互。需要一段时间,但应该可以。完成所有这些操作后,您必须希望在每个新发行版中,它们不要过多地更改DOM结构,否则您可能必须更新所有测试。

另一个选择是相信Material-UI将使组件能够正常工作并且用户可以使用。基于这种信任,您只需在测试中替换该组件即可。

是的,选项一测试用户看到的内容,但是选项二更易于维护。

以我的经验,第二种选择很好,但是当然,您的用例可能有所不同,您可能必须测试实际的组件。

这是一个如何模拟选择的示例:

jest.mock("react-select", () => ({ options, value, onChange }) => {
  function handleChange(event) {
    const option = options.find(
      option => option.value === event.currentTarget.value
    );
    onChange(option);
  }
  return (
    <select data-testid="select" value={value} onChange={handleChange}>
      {options.map(({ label, value }) => (
        <option key={value} value={value}>
          {label}
        </option>
      ))}
    </select>
  );
});
Run Code Online (Sandbox Code Playgroud)

您可以在这里阅读更多内容。

  • 如果他们嘲笑组件到这种程度,那么他们应该对组件的测试完全没有信心。我强烈建议不要采用这种方法。在这种情况下,您正在测试一个完全不同的组件。 (7认同)
  • @GiorgioPolvara-Gpx 我不同意你应该嘲笑第三方库。如果该库发生更改/损坏,我想了解它(不一定要阅读更改日志/发行说明),并且测试将如何发生。 (5认同)
  • @ GiorgioPolvara-Gpx当我采用这种方法时,您建议我很好奇知道这是否真的违反了测试库的指导原则。lib鼓励测试最终用户实际与之交互的内容(所以对我来说,更多是集成/功能测试而不是单元测试)。在您的方法中,您正在模拟外部依赖关系(这对单元测试很有用),但是如果依赖关系得到更新,则需要进行更改以对失败的软件进行成功的测试。您对此有何看法? (2认同)

小智 5

一种简单的测试方法是执行用户应该执行的操作

  • 单击选择字段。
  • 单击下拉列表中的一项。
function CustomSelect() {

  const colourOptions = [
    { value: 'orange', label: 'Orange', color: '#FF8B00' },
    { value: 'yellow', label: 'Yellow', color: '#FFC400' }
  ]

  return <Select 
    aria-label="my custom select" 
    options={colourOptions}
    //... props  
  />
}
Run Code Online (Sandbox Code Playgroud)
import { act, render, screen } from '@testing-library/react'; 
import userEvent from '@testing-library/user-event';
// another imports

test('show selected item...', async () => {
  const { getByText, getByLabelText } = render(<CustomSelect />);

  expect(getByText('Orange')).not.toBeInTheDocument();
  
  const myCustomSelect = getByLabelText(/my custom select/i);
  await act(async () => userEvent.click(myCustomSelect));

  const selectedItem = getByText('Orange');
  await act(async () => userEvent.click(selectedItem));

  expect(getByText('Orange')).toBeInTheDocument();
});
Run Code Online (Sandbox Code Playgroud)