Simplificando testes com react-select-event

giraldidev

Leonardo Giraldi Moreno Giuranno

Posted on August 7, 2024

Simplificando testes com react-select-event

A squad em que atuo faz uso do componente react-select para apresentação de campos de seleção de alguns formulários em nossas aplicações frontend. Encontramos dificuldades para implementar testes utilizando a biblioteca React Testing Library que validassem o comportamento deste campo complexo em nossos componentes.

Estávamos seguindo por uma abordagem de tentar manipular os elementos gerados pelos componentes fornecidos pelo react-select através dos métodos utilitários do React Testing Library. Porém, não obtivemos sucesso e o código dos testes estavam cada vez mais obtendo uma complexidade desnecessária.

Ao consultar a documentação do próprio React Testing Library, mais especificamente na seção do ecossistema da biblioteca, nos deparamos com a lib "react-select-event":

Lista de bibliotecas do ecossistema React Testing Library

Segundo sua descrição: "react-select-event é uma biblioteca complementar para a React Testing Library que fornece métodos auxiliares para interagir com elementos react-select.".

EUREKA!

No exemplo apresentado logo de cara, tínhamos um campo de seleção simples, que tinha como "fonte de dados" um simples array que era passado via prop options:

Exemplo de código

Este exemplo não era suficiente para nós, uma vez que nossos campos react-select consultam uma API para retornar a lista de opções com base no que foi digitado pelo usuário, ou seja, temos um cenário assíncrono em questão.

Acessamos então o repositório no GitHub da biblioteca em busca de mais exemplos.

No repositório, encontramos diversos exemplos de implementação, inclusive aquele que precisávamos:

Exemplo de código

Vamos dar uma olhada em um exemplo prático:

Considere o seguinte componente que implementa um formulário simples com apenas um campo de seleção:

import { useState } from "react";
import AsyncSelect from "react-select/async";

import { ColourOption, colourOptions } from "../data";
import { SingleValue } from "react-select";

const Form: React.FC = () => {
  const [selectedOption, setSelectedOption] = useState<string>();

  const filterColors = (inputValue: string) => {
    return colourOptions.filter((i) =>
      i.label.toLowerCase().includes(inputValue.toLowerCase())
    );
  };

  const promiseOptions = (inputValue: string) =>
    new Promise<ColourOption[]>((resolve) => {
      setTimeout(() => {
        resolve(filterColors(inputValue));
      }, 1000);
    });

  const handleChange = (option: SingleValue<ColourOption>) => {
    if (option) {
      setSelectedOption(option.label);
    }
  };

  return (
    <form>
      <label htmlFor="color">Cor</label>
      <AsyncSelect
        defaultOptions
        loadOptions={promiseOptions}
        name="color"
        onChange={handleChange}
        inputId="color"
      />
      <p>Cor selecionada: {selectedOption}</p>
    </form>
  );
};

export { Form };
Enter fullscreen mode Exit fullscreen mode

Utilizando somente os recursos disponíveis no React Testing Library, seguiríamos pelo caminho de tentar manipular diretamente o valor do input:

import { fireEvent, render, screen } from "@testing-library/react";

import { Form } from "../../components/Form";

describe("Form", () => {
  it("should present selected color", async () => {
    render(<Form />);

    const input = screen.getByLabelText("Cor");
    fireEvent.change(input, { target: { value: "purple" } });

    expect(
      await screen.findByText("Cor selecionada: Purple")
    ).toBeInTheDocument();
  });
});
Enter fullscreen mode Exit fullscreen mode

Ou tentaríamos simular o preenchimento do campo e, após, a seleção da opção via seta para baixo e tecla enter:

import { fireEvent, render, screen } from "@testing-library/react";

import { Form } from "../../components/Form";

describe("Form", () => {
  it("should present selected color", async () => {
    render(<Form />);

    const input = screen.getByLabelText("Cor");
    fireEvent.change(input, { target: { value: "purple" } });
    fireEvent.keyDown(input, { key: "ArrowDown", code: "ArrowDown" });
    fireEvent.keyDown(input, { key: "Enter", code: "Enter" });

    expect(
      await screen.findByText("Cor selecionada: Purple")
    ).toBeInTheDocument();
  });
});
Enter fullscreen mode Exit fullscreen mode

Estes foram os caminhos que tentamos trilhar. Porém, somente tivemos êxito quando adotamos o react-select-event. Com ele, o teste fica assim:

import { render, screen } from "@testing-library/react";
import selectEvent from "react-select-event";

import { Form } from "../../components/Form";

describe("Form", () => {
  it("should present selected color", async () => {
    render(<Form />);

    const input = screen.getByLabelText("Cor");
    await selectEvent.select(input, "Purple");

    expect(
      await screen.findByText("Cor selecionada: Purple")
    ).toBeInTheDocument();
  });
});
Enter fullscreen mode Exit fullscreen mode

A biblioteca ainda disponibiliza outros métodos além do select que podem atender a outros cenários de teste que você necessite implementar:

Métodos disponíveis na biblioteca react-select-event

Depois desta pequena jornada, ficamos com uma lição: "se a solução está ficando complexa, é grande a chance de estarmos trilhando o caminho errado".

💖 💪 🙅 🚩
giraldidev
Leonardo Giraldi Moreno Giuranno

Posted on August 7, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related