Testes Unitários no Front-End (React)

By: Gustavo Barbosa
Posted: March 12, 2023

Bom, sempre que se fala em criação de testes de código (isso vale mais para quem já tem uma vivência na área), geralmente o tipo mais comum que é citado é o teste unitário. Mas muitas das vezes, principalmente no período de começo de carreira, não temos se quer um norte para começar, ainda mais se tratando do Front-End. E ainda vem o questionamento: como vou escrever teste unitário pro Front-End? Por onde começo?

Com esse post, vou tentar deixar aqui, uma espécie de norte para quem não sabe muito bem por onde começar.

Mas antes de tudo, vamos introduzir o porque disso.

Como um teste é estruturado?

Teste envolve verificar se o seu código está funcionando como deveria, comparando a saída esperada com a saída real.

O que testar?

Em geral, o seu teste deve cobrir os seguintes aspectos do seu código:

  1. Se o componente renderiza com ou sem Props.
  2. Como o componente renderiza com mudança de estados.
  3. Como um componente React’s (joguete de palavras) com interações do usuário

O que não testar?

Testar a maior porcentagem do seu código é importante, mas aqui estão algumas coisas que você não precisa testar:

  1. Implementações atuais: você não precisa testar algo que já está funcionando no sistema. Nesse caso, basta testar se o componente está se comportando corretamente. Caso contrário, isso poderia ter um grande impacto a depender da base de código e isso piora com uma base de código que você não conhece. Para exemplificar um pouco mais: digamos que você queira classificar uma matriz com o clique de um botão. Não há necessidade de testar a lógica de classificação real. Você só testa se a função foi chamada e se as mudanças de estado estão sendo renderizadas corretamente.
  2. Bibliotecas de terceiros: sim, as dependências que instalamos no nosso projeto. Se você estiver utilizando bibliotecas de terceiros, como Material UI, não há necessidade de testá-las - elas já devem ser experimentadas e testadas. Isso não quer dizer que as bibliotecas não veem com bugs mas entende-se que elas são testadas antes de serem disponibilizadas para uso.

Com essas informações, ainda sim, parece algo complicado, mas com exemplos acho que o entendimento fica melhor.

Qualquer teste em React, não importa o quão complicado seja, segue a seguinte estrutura:

  1. Renderiza o componente
  2. Obtém um elemento do componente e simula qualquer interação do usuário
  3. Escreve-se o que é esperado do componente

Bora pros exemplos!

Como configurar nosso projeto

Primeiro, vamos criar o projeto com Vite. Nesse post, não iremos entrar em detalhes sobre o porque escolhi o Vite para começar o projeto não mas vou deixar o link aqui com a explicação dos problemas que ele propõe a resolver. Nota pra mim mesmo: escrever algo que explique Vite e dar meu parecer. Caso você queira criar o projeto usando a boa e velha CLI create-react-apptá tranquilo que vai funcionar do mesmo jeito, verdade vai vir até com as libs que vamos utilizar a princípio.

Com o terminal aberto, rode o comando:

npm create vite
ou
yarn create vite

Após isso, bastar seguir as instrução que serão apresentadas no terminal.

Caso queira iniciar o projeto já com o nome e definir que vai usar React, rode o comando:

# npm 6.x
npm create vite@latest react-basic-tests --template react

# npm 7+, extra double-dash is needed:
npm create vite@latest react-basic-tests -- --template react

# yarn
yarn create vite react-basic-tests --template react

Com o projeto criado, vamos adicionar uma biblioteca oficial de testes unitários do Vite, o Vitest.

Rode o comando:

npm install vitest --save-dev
ou
yarn add vitest -D

Depois, adicione o script de teste no package.json:

{
  ...
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    

Para assegurar que está tudo funcionando vamos criar um arquivo que tenha o sufixo test, e.g App.test.tsx e coloque o seguinte conteúdo:

import { describe, it, expect } from 'vitest';

describe('something truthy and falsy', () => {
  it('true to be true', () => {
    expect(true).toBe(true);
  });

  it('false to be false', () => {
    expect(false).toBe(false);
  });
});

Caso não entenda a estrutura acima, não se preocupe pois vamos abordar ela melhor mais a diante.

Você pode ver que o Vitest vem com suítes de teste (here: describe), casos de teste (here: it) e assertions (here: expect().toBe()). Se você já usou o Jest antes, deve estar familiarizado com ele, porque o Vitest atua como um substituto para ele.

Você já pode executar seus testes na linha de comando com yarn test. Eles devem ficar verdes.

Com isso, precisamos realizar apenas mais uma configuração para testar nossos componentes.

Vitest integrado com React Testing Library

Como o React Testing Library testa os componentes do React, precisamos habilitar o HTML no Vitest com uma biblioteca como jsdom. Primeiro, instale a biblioteca na linha de comando:

npm install jsdom --save-dev
ou
yarn add jsdom -D

Segundo, inclua o jsdom nas configurações do Vite:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',
  },
});

Terceiro, instale a lib React Testing Library com o comando:

npm install @testing-library/react @testing-library/jest-dom --save-dev
ou
yarn add -D @testing-library/react @testing-library/jest-dom

Quarto, adicione um arquivo de configuração de teste na pasta __tests__ com o nome de setup.js e adicione a seguinte implementação:

import { expect, afterEach } from 'vitest';
import { cleanup } from '@testing-library/react';
import matchers from '@testing-library/jest-dom/matchers';

// extends Vitest's expect method with methods from react-testing-library
expect.extend(matchers);

// runs a cleanup after each test case (e.g. clearing jsdom)
afterEach(() => {
  cleanup();
});

E por último, inclua este novo arquivo de configuração de teste no arquivo de configuração do Vite. Além disso, faça todas as importações do Vitest globais, para que você não precise mais realizar essas importações (por exemplo, esperar) em cada arquivo manualmente:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  test: {
    globals: true, // add this
    environment: 'jsdom',
    setupFiles: 'src/__tests__/setup.js', // add this
  },
});

Com isso, agora podemos escrever testes para os nossos componentes.

Como escrever seu primeiro teste para um aplicativo React

Vamos escrever um teste para o seguinte componente:

interface Props {
  subtitle: string;
}

const Header = ({ subtitle = "first test" }: Props): JSX.Element => {
  return <h2>{subtitle}</h2>;
};

export default Header;

Crie esse componente na pasta components.

Aqui, só precisamos testar se o elemento h2 renderiza. Agora, onde devemos escrever os testes? Podemos escrevê-los dentro de uma pasta __tests__ em qualquer lugar da pasta src. O arquivo de teste só precisa ter uma extensão .test.js/jsx e o executor do teste irá pegá-lo.

Esta é a aparência do teste em nosso arquivo FirstTest.test.tsx:

import { render, screen } from "@testing-library/react";
import FirstTest from "../components/FirstTest";

test("Example 1 renders successfully", () => {
  render(<FirstTest subtitle="first test" />);

  const element = screen.getByText(/first test/i);

  expect(element).toBeInTheDocument();

  screen.debug();
});

Primeiro, importe os métodos necessários. O método test() contém um teste individual. Leva o nome do teste e uma função de retorno de chamada como os dois argumentos.

Agora, seguindo a estrutura mencionada acima, renderize o componente que você está testando usando o método render. Em seguida, utilize o objeto de tela para fazer uma consulta a um elemento. Neste caso, é o elemento h2. Nossa consulta obtém um elemento contendo texto que corresponde ao regex /first test/i (i significa ignorar maiúsculas e minúsculas).

Por fim, faça a declaração usando o método expect. Esperamos que o elemento esteja no documento e está, então o teste passa.

Existem muitas outras afirmações que você pode fazer em seus testes. Você pode ler mais sobre eles aqui. Além disso, você pode encontrar uma lista de maneiras de consultar um elemento aqui. Usaremos alguns deles em nossos exemplos adicionais.

Como testar com dados “mockados” no React

Aqui, temos um componente com um prop data que exibe uma lista de itens. Vamos supor que esses dados venham do back-end e seu componente esteja exibindo esses dados.

import { User } from "../mock/user";

const TestWithMockData = (data: Array<User>) => {
  return (
    <div>
      <ul>
        {data.map((user) => (
          <li key={user.id}>
            {user.id}
            {user.first_name}
            {user.last_name}
            {user.email}
          </li>
        ))}
      </ul>
    </div>
  );
};

export default TestWithMockData;

Ao escrever testes para um componente com props, você precisa passar alguns dados fictícios ao renderizar esse componente que pertence à sua funcionalidade. Aqui, um objeto em nossos dados contém quatro campos, então passamos alguns dados fictícios aqui.

export interface User {
  id: number;
  first_name: string;
  last_name: string;
  email: string;
  age: number;
}
[];

export const mockData: Array<User> = [
  {
    id: 1,
    first_name: "Fletcher",
    last_name: "McVanamy",
    email: "mmcvanamy0@e-recht24.de",
    age: 30,
  },
  {
    id: 2,
    first_name: "Clarice",
    last_name: "Harrild",
    email: "charrild1@dion.ne.jp",
    age: 35,
  },
];

Observe que os dados reais podem conter milhares de registros, mas os dados fictícios só precisam ser relevantes para o que você deseja testar. Agora, vamos escrever o teste e verificar se a lista é renderizada.

import { render, screen } from "@testing-library/react";
import TestWithMockData from "../components/TestWithMockData";
import { mockData } from "../mock/user";

test("List renders successfully", () => {
  render(<TestWithMockData users={mockData} />);
  expect(screen.getByText(/fletcher/i)).toBeInTheDocument();
});

Como testar com dados mockados cobrindo todas as branchs (ramificações)

Com ramificações, quero dizer que um componente pode apresentar estilos e tipos de visualizações de dados diferentes.

Vamos introduzir algumas ramificações no componente acima. Teremos outro prop, displayUnorderedList, que determina se deve exibir uma lista ordenada ou não ordenada. Também processaremos Sênior para idade > 50 anos e Não Sênior caso contrário.

import { BranchingComponentProps } from "../mock/user";

const TestWithMockDataWithBranching = ({
  data,
  displayUnorderedList,
  handleClick,
}: BranchingComponentProps) => {
  return (
    <div>
      {displayUnorderedList ? (
        <ul>
          {data.map((item) => (
            <li key={item.id}>
              {item.id}
              {item.first_name}
              {item.last_name}
              <a
                onClick={() => {
                  console.log("email link clicked");
                  handleClick();
                }}
              >
                {item.email}
              </a>

              {item.age > 50 ? "Senior" : "Not senior"}
            </li>
          ))}
        </ul>
      ) : (
        <ol>
          {data.map((item) => (
            <li key={item.id}>Last name: {item.last_name}</li>
          ))}
        </ol>
      )}
    </div>
  );
};

export default TestWithMockDataWithBranching;

Bora ver o relatório de cobertura nesse momento:

An image from Notion

Teste com falha: não é possível encontrar o elemento

Você pode ver que a Linha 7 (ou seja, a parte da lista não ordenada) não é coberta por nossos testes existentes. E nosso teste anterior também falhou, pois não conseguiu encontrar o fletcher no componente.

Porque isto é assim? Em nosso teste anterior, não passamos a propriedade displayUnorderedList para o componente, então ela é nula. Portanto, o componente renderiza a lista ordenada e não contém o first_name.

Então, vamos escrever outro teste cobrindo a parte da lista ordenada.

import { render, screen } from "@testing-library/react";
import TestWithMockDataWithBranching from "../components/TestWithMockDataWithBranching";
import { mockData } from "../mock/user";

test("List renders successfully", () => {
  render(
    <TestWithMockDataWithBranching
      data={mockData}
      displayUnorderedList={false}
    />
  );
  expect(screen.getByText(/McVanamy/i)).toBeInTheDocument();
});

Aqui, passe o valor props como false para renderizar a lista ordenada. Além disso, adicione a propriedade displayUnorderedList ao teste com falha e passe o valor true.

Agora, todos os nossos testes passam com 100% de cobertura.

An image from Notion

Uma linha ainda não é coberta pelos casos de teste, que é a lógica de ramificação para idade. Portanto, adicione outro registro aos dados fictícios com idade superior a 50.

{
    "id": 3,
    "first_name": "Amby",
    "last_name": "Emmer",
    "email": "aemmer2@buzzfeed.com",
    "age": 67
}

Agora, todos os nossos testes devem passar com 100% de cobertura, mesmo que nosso arquivo esteja com erro de tipagem do componente esperando a terceira prop que chamamos de handleClick. Vamos ver agora como poderemos implementar o teste que dependeria da ação de um usuário, no caso um click.

Como testar interações do usuário no React

A parte mais importante do teste de qualquer aplicativo de interface do usuário é testar seu comportamento com várias interações do usuário. Quase todas as funcionalidades em um aplicativo de interface do usuário envolvem interações do usuário.

Você pode usar a biblioteca de eventos do usuário para simular as interações do usuário. Ele possui métodos para simular vários eventos do usuário, como clicar, digitar, passar o mouse e assim por diante.

Primeiro, precisamos instalar a lib:

npm install --save-dev @testing-library/user-event
ou
yarn add -D @testing-library/user-event

Podemos usar esta biblioteca para simular eventos do usuário. Em nossos exemplos, vamos interagir com diferentes elementos, principalmente elementos de input e botão.

Como testar uma chamada de função ao clicar em um elemento

Em nosso componente acima, precisamos TestWithMockData, tornar o campo de email clicável e chamar uma função handleClick nele. Isso será passado como props para o componente. Lá, substitua {item.email} por:

<a onClick={() => {
    console.log("email link clicked")
    handleClick()
}}>{item.email}</a>

Agora, nossa cobertura de teste é atingida. Para cobrir este cenário, escreva o seguinte teste:

test("Email link click handler called", async () => {
  const mockHandleCLick = vi.fn();
  render(
    <TestWithMockDataWithBranching
      data={mockData}
      displayUnorderedList={true}
      handleClick={mockHandleCLick}
    />
  );

  await userEvent.click(screen.getByText(/mmcvanamy0@e-recht24.de/i));
  expect(mockHandleCLick).toHaveBeenCalled();
});

Primeiro, crie a simulação da função handleClick usando vi.fn(). Não precisamos da implementação real do método, pois queremos apenas testar o comportamento do componente. Então, criamos um mock vazio e passamos o mesmo como props. Leia mais sobre funções de simulação aqui.

Agora, consulte o elemento <a> por texto (qualquer e-mail dos dados fictícios). Use o método click() para simular um evento de clique. Use await, pois a simulação de um evento do usuário é uma operação assíncrona.

Escreva uma asserção no final para verificar se o método foi chamado. O método foi chamado, então nosso teste passa com 100% de cobertura.

Se você tá usando Typescript igual eu, os outros dois testes, na IDE, estão acusando erro de tipagem, falando que tá faltando a prop handleClick. Pra resolver isso, basta passar uma função vazia pra sua IDE não ficar com erro aí 👍.

Segue o código completo:

import { vi } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import TestWithMockDataWithBranching from "../components/TestWithMockDataWithBranching";
import { mockData } from "../mock/user";

test("List renders successfully", () => {
  render(
    <TestWithMockDataWithBranching
      data={mockData}
      displayUnorderedList={true}
      handleClick={() => {}}
    />
  );
  expect(screen.getByText(/fletcher/i)).toBeInTheDocument();
});

test("Ordered list renders", () => {
  render(
    <TestWithMockDataWithBranching
      data={mockData}
      displayUnorderedList={false}
      handleClick={() => {}}
    />
  );

  expect(screen.getByText(/McVanamy/i)).toBeInTheDocument();
});

test("Email link click handler called", async () => {
  const mockHandleCLick = vi.fn();
  render(
    <TestWithMockDataWithBranching
      data={mockData}
      displayUnorderedList={true}
      handleClick={mockHandleCLick}
    />
  );

  await userEvent.click(screen.getByText(/mmcvanamy0@e-recht24.de/i));
  expect(mockHandleCLick).toHaveBeenCalled();
});

Vamos ver a seguir como podemos lidar com outros coisas que o usuário possa interagir.

Como testar campos de entrada (input) e botões

Até agora, usamos apenas um método de consulta de elementos – getByText(). Agora, vamos ver como você pode consultar campos de entrada e botões.

<input placeholder='Enter name'/>
<button> Submit </button>

Para começar a testar esses elementos, você pode fazer o seguinte:

const inputElement = screen.getByRole('textbox')

getByRole localiza um elemento pelo tipo papel (ou “cargo”) fornecido. Nesse caso, o papel textbox significa o elemento de entrada (input).

Como o papel é determinado? Cada elemento tem uma função definida, portanto, não é necessário especificar um atributo de função explícito. Você pode ver uma lista de papéis para diferentes elementos aqui. Seja qual for o elemento que você deseja, basta fazer getByRole e consultar a lista.

Para o botão, a função padrão é 'button', como você pode ver aqui:

const button = screen.getByRole('button')

E se adicionarmos outro elemento de entrada, <input placeholder='Enter description'/>? O executor de teste agora lançará um erro dizendo que há dois elementos com a mesma função. O que devemos fazer em tal cenário? Use outra consulta getByPlaceholderText().

const nameInput = screen.getByPlaceholderText(/enter name/i);
const descrInput = screen.getByPlaceholderText(/enter description/i);

Você também pode usar getByLabelText() se sua entrada tiver um rótulo (label).

<label htmlFor='password'> Enter password</label>
<input type='password' id='password'/>
const passwordInput = screen.getByLabelText(/enter password/i);

Para consultar botões, podemos fazer screen.getByRole('button').

<button> Submit </button>
<button> Apply</button>

Como temos dois botões aqui, apenas fazer getByRole gerará um erro. Podemos passar um objeto ‘name’ para pegar os botões separadamente.

const submitButton = screen.getByRole('button', { name: /submit/i });
const applyButton = screen.getByRole('button', { name: /apply/i });

A opção name pode conter o rótulo de um elemento de formulário, o texto de um botão ou o valor do atributo aria-label de qualquer elemento. Também podemos fazer getByText() para um botão.

const submitButton = screen.getByText(/submit/i);

Como testar atualizações de estado no React

Vimos como consultar elementos de formulário como entrada e botão. Agora, vamos simular algumas interações do usuário e testar atualizações de estado. O que quero dizer com testar atualizações de estado?

As atualizações de estado fazem com que um componente seja renderizado novamente. Portanto, quando sua funcionalidade realizar uma atualização de estado, você deve testar como o componente está se comportando devido à mudança de estado.

Primeiro, vamos dar um exemplo simples onde definimos o estado assim que o componente é carregado – ou seja, no bloco useEffect.

const TestingStateChange = () => {
    const [loaded, setLoaded] = useState(false)
    useEffect(() => {
        setLoaded(true)
    }, [])
  return (
    <div>
        {loaded && <h3> Page Loaded </h3>}
    </div>
  )
}

Aqui, todo o seu componente, começando da instrução useEffect até o final, é descoberto. Quando você escreve o teste a seguir, não apenas a parte HTML é coberta, mas também a parte useEffect onde você define o estado.

test("Testing page load", () => {
    render(<TestingStateChange/>)
    expect(screen.getByText(/page loaded/i)).toBeInTheDocument();
})

É assim que você testa as atualizações de estado. Você testa se o componente se comporta conforme o esperado com uma mudança de estado.

Como testar uma mudança de estado ao clicar em um botão

Vamos ter um botão e um texto que alterna com o clique do botão.

const [toggleTextVisible, setToggleTextVisible] = useState(false)

Vamos escrever o teste para isso:

test("Toggle text visible", async () => {
    render(<TestingStateChange/>);
    await userEvent.click(screen.getByText(/toggle text/i));
    expect(screen.getByText(/text visible/i)).toBeInTheDocument();
})

Com userEvent, simulamos o clique de um botão e afirmamos se o texto está visível.

Agora, vamos testar outro cenário onde verificamos se o botão acima está desabilitado ao clicar em outro botão.

const [btnDisabled, setBtnDisabled] = useState(false);

Adicione um atributo disabled={btnDisabled} ao botão acima e crie outro botão que controle seu valor.

<button onClick={() => { setBtnDisabled(!btnDisabled) }}> 
      Toggle button disabled 
</button>

Use o método toBeDisabled() para testar se o botão está desabilitado.

test("Button disabled on click", async () => {
    render(<TestingStateChange/>)
    await userEvent.click(screen.getByText(/toggle button disabled/i));
    expect(screen.getByText(/toggle text/i)).toBeDisabled();
})

Como testar se um elemento foi adicionado a uma lista

Vamos fazer outra assertiva. Aqui, verificaremos se um elemento foi adicionado a uma lista. Teremos alguns dados e criaremos um estado com os dados como valor inicial. Vamos exibi-lo enquanto também temos um botão para adicionar um elemento a ele.

const [elements, setElements] = useState(data);

Adicione um atributo data-testid='record' a cada registro para ajudar a consultar o elemento. Vamos escrever o teste para isso:

test("Element added to the list", async () => {
    render(<TestingStateChange/>)
    expect(screen.getAllByTestId('record').length).toBe(3);

    await userEvent.click(screen.getByText(/add to list/i));
    expect(screen.getAllByTestId('record').length).toBe(4);
})

Para obter vários elementos com a mesma consulta, use o método getAllBy…. Nesse caso, há vários elementos com o registro test-id, portanto, usar o método getAllByTestId() obtém uma lista de todos esses elementos.

Aqui, verificamos o comprimento da lista. Podemos adicionar outra asserção para verificar se o novo elemento está visível.

expect(screen.getByText(/new element/i)).toBeInTheDocument();

Como testar se um elemento foi removido de uma lista

Agora, vamos adicionar outro botão que remove um elemento da lista.

<button onClick={() => {
	setElements(elements.filter((element) => element.id !== 4));
}} > Remove from list </button>

O teste para isso vai ficar assim:

test("Element removed from list", async () => {
    render(<TestingStateChange/>)
    await userEvent.click(screen.getByText(/remove from list/i));
    expect(screen.getAllByTestId('record').length).toBe(2);
})

Aqui, testamos se o comprimento da lista foi reduzido. Todos os nossos testes passam com 100% de cobertura.

An image from Notion

Como testar chamadas a API no React

As chamadas de API são uma parte importante de qualquer aplicativo de interface do usuário. Vamos entender como testar uma chamada de API.

Neste exemplo, temos um arquivo data.json que possui alguns dados. Faremos uma chamada de API para buscar esses dados.

Para demonstração, vamos salvar este arquivo apenas no servidor local (dentro da pasta pública), mas o procedimento continua o mesmo ao buscar um arquivo de um servidor remoto.

Usaremos o fetch para fazer a chamada da API.

fetch("http://localhost:3000/data.json")
  .then(res => {
      return res.json();
  })
  .then(data => {
      // Store the data in state
  })

Agora, vamos ter um estado para armazenar os dados e renderizá-los.

const [data, setData] = useState([])
<div>
    {data.map(item => (
        <div>
            {item.name}
        </div>
    ))}
</div>

Como escrevemos o teste para isso? Uma maneira é zombar do método de busca e fornecer nossa própria implementação simulada. Mas e se houver várias chamadas de Fetch API no componente? Você não pode ter a mesma implementação simulada para todos.

Portanto, colocamos a chamada de busca em uma função separada e temos uma implementação fictícia dessa função. Colocamos nossa chamada de API em um método FetchData que retorna a promessa retornada por fetch.

Colocamos o método em um arquivo Services.js separado e o exportamos de lá.

Agora, quando chamamos esse método dentro do nosso componente, ele fica assim:

const [data, setData] = useState([])

useEffect(() => {
    FetchData().then(data => {
        setData(data);
    })
})

return (
  <div>
      {data.map(item => (
          <div>
              {item.name}
          </div>
      ))}
  </div>
)

Agora, vamos escrever o teste para isso. O principal é zombar do método FetchData. Primeiro, importe todos os métodos de Services.js como um módulo.

import * as services from '../utils/Services'

Vamos simular nossa função usando jest.spyOn(). O método recebe dois argumentos, o objeto e o nome do método como uma string.

const mockFetchData = jest.spyOn(services, 'FetchData')
	.mockImplementation(async () => {
	    return [{
	        name: 'kunal'
	    }];
	})

spyOn cria uma simulação vazia do método. Agora, fornecemos nossa implementação simulada, onde retornamos alguns dados simulados. Isso será chamado quando nosso componente for renderizado.

render(<TestingAPICalls/>)
expect(mockFetchData).toHaveBeenCalled();

Usamos toHaveBeenCalled() para testar se o método foi chamado. Foi - então o teste passa. Agora, para testar o comportamento do nosso componente, vamos testar se o nome foi renderizado.

expect(screen.getByText(/vite/i)).toBeInTheDocument();

Nesse caso, o teste falha porque não consegue encontrar o elemento. Porque isto é assim?

An image from Notion

Teste com falha: não é possível encontrar o elemento

Como as chamadas de API e as atualizações de estado são assíncronas, o texto ainda não foi renderizado. Para testar o comportamento assíncrono, envolva a consulta em um bloco waitFor().

await waitFor(() => {
    expect(screen.getByText(/gus/i)).toBeInTheDocument();
})

waitFor faz exatamente o que o nome sugere. Ele aguarda a conclusão do comportamento assíncrono antes de fazer a consulta. Agora, nosso teste passa com 100% de cobertura.

An image from Notion

100% de cobertura para chamadas de API de teste

Você pode encontrar o código completo no GitHub.

Conclusão

Ufa! Essa foi longa. Mas precisava ser. Eu expliquei, através de exemplos básicos, como você pode começar a escrever testes em React.

Este artigo começou com um teste básico com uma declaração simples e explicou como usar dados fictícios para servir ao propósito do seu teste.

Também expliquei como simular interações do usuário, como testar o comportamento de um componente em atualizações de estado e, por último, como testar chamadas de API. Espero que todos esses exemplos o ajudem a escrever testes para seu próximo projeto.