web-dev-qa-db-fra.com

comment tester react-select avec 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;

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"));
});

Cet exemple minimal fonctionne comme prévu dans le navigateur, mais le test échoue. Je pense que le gestionnaire onChange n'est pas appelé. Comment puis-je déclencher le rappel onChange dans le test? Quelle est la meilleure façon de trouver l'élément sur lequel déclencher fireEvent? Je vous remercie

19
user2133814

Cela devait être la question la plus posée sur RTL: D

La meilleure stratégie consiste à utiliser jest.mock (ou l'équivalent dans votre framework de test) pour simuler la sélection et rendre une sélection HTML à la place.

Pour plus d'informations sur la raison pour laquelle c'est la meilleure approche, j'ai écrit quelque chose qui s'applique également à ce cas. L'OP a demandé une sélection dans Material-UI mais l'idée est la même.

Question d'origine et ma réponse:

Parce que vous n'avez aucun contrôle sur cette interface utilisateur. Il est défini dans un module tiers.

Donc, vous avez deux options:

Vous pouvez déterminer le code HTML créé par la bibliothèque de matériaux, puis utiliser container.querySelector pour trouver ses éléments et interagir avec lui. Cela prend un certain temps, mais cela devrait être possible. Après avoir fait tout cela, vous devez espérer qu'à chaque nouvelle version, ils ne modifient pas trop la structure DOM ou vous devrez peut-être mettre à jour tous vos tests.

L'autre option consiste à faire confiance à Material-UI pour créer un composant qui fonctionne et que vos utilisateurs peuvent utiliser. Sur la base de cette confiance, vous pouvez simplement remplacer ce composant dans vos tests pour un plus simple.

Oui, l'option un teste ce que l'utilisateur voit, mais l'option deux est plus facile à maintenir.

D'après mon expérience, la deuxième option est très bien, mais bien sûr, votre cas d'utilisation peut être différent et vous devrez peut-être tester le composant réel.

Voici un exemple de la façon dont vous pouvez vous moquer d'une sélection:

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>
  );
});

Vous pouvez en savoir plus ici .

14

Dans mon projet, j'utilise react-testing-library et jest-dom. J'ai rencontré le même problème - après une enquête, j'ai trouvé une solution, basée sur le fil: https://github.com/airbnb/enzyme/issues/4

Notez que la fonction de niveau supérieur pour le rendu doit être asynchrone, ainsi que les étapes individuelles.

Il n'est pas nécessaire d'utiliser l'événement focus dans ce cas, et cela permettra de sélectionner plusieurs valeurs.

De plus, il doit y avoir un rappel asynchrone dans 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');

  ( ... )

}
15
momimomo

Semblable à la réponse de @ momimomo, j'ai écrit un petit assistant pour choisir une option parmi react-select dans TypeScript.

Fichier d'assistance:

import { getByText, fireEvent, waitForElement } 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 waitForElement(() => getByText(container, optionText));
    fireEvent.click(getByText(container, optionText));
}

Usage:

export const MyComponent: React.FunctionComponent = () => {
    return (
        <div data-testid="day-selector">
            <Select {...reactSelectOptions} />
        </div>
    );
};
it('can select an option', async () => {
    const { getByTestId } = render(<MyComponent />);
    // Open the react-select options then click on "Monday".
    await selectOption(getByTestId('day-selector'), 'Monday');
});
3
Vestride
export async function selectOption(container: HTMLElement, optionText: string) {
  let listControl: any = '';
  await waitForElement(
    () => (listControl = container.querySelector('.Select-control')),
  );
  fireEvent.mouseDown(listControl);
  await wait();
  const option = getByText(container, optionText);
  fireEvent.mouseDown(option);
  await wait();
}

REMARQUE: conteneur: conteneur pour la zone de sélection (par exemple: conteneur = getByTestId ('seclectTestId'))

0
tushar kumar