O autor selecionou Vets Who Code para receber uma doação como parte do programa Write for DOnations.
Introdução
Obter uma cobertura de teste sólida é imperativo para construir confiança em sua aplicação web. Jest é um executor de testes JavaScript que fornece recursos para escrever e executar testes. React Testing Library oferece um conjunto de ajudantes de teste que estruturam seus testes com base nas interações do usuário, em vez de detalhes de implementação de componentes. Tanto Jest quanto React Testing Library vêm pré-embalados com Create React App e seguem o princípio orientador de que testar aplicativos deve se assemelhar a como o software será usado.
Neste tutorial, você testará código assíncrono e interações em um projeto de exemplo contendo vários elementos de interface do usuário. Você usará o Jest para escrever e executar testes unitários, e implementará o React Testing Library como uma biblioteca de DOM (Document Object Model) auxiliar para lidar com a interação com componentes.
Pré-requisitos
Para completar este tutorial, você precisará de:
-
Node.js versão 14 ou superior instalado em sua máquina local. Para instalar o Node.js no macOS ou Ubuntu 18.04, siga as etapas em Como Instalar o Node.js e Criar um Ambiente de Desenvolvimento Local no macOS ou a seção Instalando Usando um PPA de Como Instalar o Node.js no Ubuntu 18.04.
-
npm versão 5.2 ou superior em sua máquina local, que você precisará usar Create React App e
npx
no projeto de exemplo. Se você não instalounpm
junto comNode.js
, faça isso agora. Para Linux, use o comandosudo apt install npm
.- Para que os pacotes do
npm
funcionem neste tutorial, instale o pacotebuild-essential
. Para Linux, use o comandosudo apt install build-essential
.
- Para que os pacotes do
-
Git instalado em sua máquina local. Você pode verificar se o Git está instalado em seu computador ou passar pelo processo de instalação para seu sistema operacional com Como Instalar o Git no Ubuntu 20.04.
-
Familiaridade com o React, que você pode desenvolver com a série Como Programar em React.js. Como o projeto de exemplo é inicializado com Create React App, você não precisa instalá-lo separadamente.
-
Alguma familiaridade com o Jest como um executor ou framework de testes é útil, mas não obrigatória. Como o Jest é pré-empacotado com o Create React App, você não precisa instalá-lo separadamente.
Passo 1 — Configurando o Projeto
Neste passo, você irá clonar um projeto de exemplo e iniciar a suíte de testes. O projeto de exemplo utiliza três ferramentas principais: Create React App, Jest e React Testing Library. Create React App é usado para inicializar uma aplicação React de página única. Jest é usado como o executor de testes, e React Testing Library fornece ajudantes de teste para estruturar testes em torno de interações do usuário.
Para começar, você irá clonar um aplicativo React pré-construído do GitHub. Você trabalhará com o aplicativo Doggy Directory, que é um projeto de exemplo que utiliza a Dog API para construir um sistema de busca e exibição para uma coleção de imagens de cachorro baseadas em uma raça específica.
Para clonar o projeto do Github, abra seu terminal e execute o seguinte comando:
Você verá uma saída semelhante a esta:
OutputCloning into 'doggy-directory'...
remote: Enumerating objects: 64, done.
remote: Counting objects: 100% (64/64), done.
remote: Compressing objects: 100% (48/48), done.
remote: Total 64 (delta 21), reused 55 (delta 15), pack-reused 0
Unpacking objects: 100% (64/64), 228.16 KiB | 3.51 MiB/s, done.
Mude para a pasta doggy-directory
:
Instale as dependências do projeto:
O comando npm install
irá instalar todas as dependências do projeto definidas no arquivo package.json
.
Após instalar as dependências, você pode visualizar a versão implantada do aplicativo ou você pode executar o aplicativo localmente com o seguinte comando:
Se você optar por executar o aplicativo localmente, ele será aberto em http://localhost:3000/
. Você verá a seguinte saída no terminal:
OutputCompiled successfully!
You can now view doggy-directory in the browser.
Local: http://localhost:3000
On Your Network: http://network_address:3000
Depois de iniciar, a página inicial do aplicativo terá esta aparência:
O projeto foi instalado as dependências e o aplicativo está agora em execução. Em seguida, abra um novo terminal e execute os testes com o seguinte comando:
O comando npm test
inicia os testes em um modo interativo de observação com o Jest como seu executor de testes. Quando no modo de observação, os testes são executados automaticamente após a alteração de um arquivo. Os testes serão executados sempre que você alterar um arquivo e informarão se essa alteração passou nos testes.
Após executar o npm test
pela primeira vez, você verá esta saída no terminal:
OutputNo tests found related to files changed since last commit.
Press `a` to run all tests, or run Jest with `--watchAll`.
Watch Usage
› Press a to run all tests.
› Press f to run only failed tests.
› Press q to quit watch mode.
› Press p to filter by a filename regex pattern.
› Press t to filter by a test name regex pattern.
› Press Enter to trigger a test run.
Agora que você tem o exemplo de aplicativo e conjunto de testes em execução, você pode começar a testar com a página inicial.
Passo 2 — Testando a Página Inicial
Por padrão, o Jest procurará por arquivos com o sufixo .test.js
e arquivos com o sufixo .js
nas pastas __tests__
. Quando você fizer alterações nos arquivos de teste relevantes, elas serão detectadas automaticamente. À medida que os casos de teste são modificados, a saída será atualizada automaticamente. O arquivo de teste preparado para o projeto de exemplo doggy-directory
está configurado com código mínimo antes de você adicionar paradigmas de teste. Neste passo, você escreverá testes para verificar se a página inicial do aplicativo será carregada antes de realizar uma pesquisa.
Abra src/App.test.js
no seu editor para ver o seguinte código:
A minimum of one test block is required in each test file. Each test block accepts two required parameters: the first argument is a string representing the name of the test case; the second argument is a function that holds the expectations of the test.
Dentro da função, há um método render
que o React Testing Library fornece para renderizar seu componente no DOM. Com o componente que você deseja testar renderizado no DOM do ambiente de teste, agora você pode começar a escrever código para assert contra a funcionalidade esperada.
Você adicionará um bloco de teste ao método render
que testará se a página de destino é renderizada com precisão antes de quaisquer chamadas de API ou seleções serem feitas. Adicione o código destacado abaixo do método render
:
A função expect
é usada toda vez que você deseja verificar um determinado resultado, e ela aceita um único argumento representando o valor que seu código produz. A maioria das funções expect
é associada a uma função matcher para afirmar algo sobre um valor específico. Para a maioria dessas asserções, você usará matchers adicionais fornecidos por jest-dom para tornar mais fácil verificar aspectos comuns encontrados no DOM. Por exemplo, .toHaveTextContent
é o matcher para a função expect
na primeira linha, enquanto getByRole("heading")
é o seletor para pegar o elemento DOM.
O React Testing Library fornece o objeto screen
como uma maneira conveniente de acessar as consultas pertinentes necessárias para afirmar contra o ambiente DOM de teste. Por padrão, o React Testing Library fornece consultas que permitem localizar elementos dentro do DOM. Existem três categorias principais de consultas:
getBy*
(mais comumente usado)queryBy*
(usado ao testar a ausência de um elemento sem lançar um erro)findBy*
(usado ao testar código assíncrono)
Cada tipo de consulta serve a um propósito específico que será definido posteriormente no tutorial. Neste passo, você se concentrará na consulta getBy*
, que é o tipo de consulta mais comum. Para ver uma lista exaustiva das diferentes variações de consulta, você pode revisar o guia de consulta do React.
Abaixo está uma imagem anotada da página inicial do Diretório de Cães indicando cada seção que o primeiro teste (na renderização da página inicial) abrange:
Cada função expect
está fazendo uma asserção contra o seguinte (mostrado na imagem anotada acima):
- Você espera que o elemento com o papel de cabeçalho tenha uma correspondência de substring de Diretório de Cães.
- Você espera que a entrada select tenha um valor de exibição exato de Selecione uma raça.
- Você espera que o botão Buscar esteja desativado, pois uma seleção não foi feita.
- Você espera que a imagem de espaço reservado esteja presente no documento, já que uma busca ainda não foi realizada.
Quando terminar, salve o arquivo src/App.test.js
. Como os testes estão sendo executados no modo de observação, as alterações serão registradas automaticamente. Se as alterações não forem registradas automaticamente, talvez seja necessário parar e reiniciar o conjunto de testes.
Agora, ao visualizar seus testes no terminal, você verá a seguinte saída:
Output PASS src/App.test.js
✓ renders the landing page (172 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.595 s, estimated 5 s
Ran all test suites related to changed files.
Watch Usage: Press w to show more.
Neste passo, você escreveu um teste inicial para verificar a visualização inicial da página de destino do Diretório de Cachorros. No próximo passo, você aprenderá como simular uma chamada de API para testar código assíncrono.
Passo 3 — Simulando o Método fetch
Neste passo, você revisará uma abordagem para simular o método fetch
do JavaScript. Embora existam várias maneiras de fazer isso, esta implementação usará os métodos spyOn
e mockImplementation
do Jest.
Quando você depende de APIs externas, há uma chance de que a API deles fique fora do ar ou leve um tempo para retornar uma resposta. Simular o método fetch
fornece um ambiente consistente e previsível, dando mais confiança aos seus testes. Um mecanismo de simulação de API é necessário para executar corretamente testes que usam uma API externa.
Nota: Em um esforço para manter este projeto simplificado, você irá simular o método fetch. No entanto, é aconselhável usar uma solução mais robusta como o Mock Service Worker (MSW) ao simular código assíncrono para bases de código maiores e prontas para produção.
Abra o arquivo src/mocks/mockFetch.js
no seu editor para revisar como o método mockFetch
funciona:
O método mockFetch
retorna um objeto que se assemelha de perto à estrutura do que uma chamada de fetch
retornaria em resposta a chamadas de API dentro da aplicação. O método mockFetch
é necessário para testar a funcionalidade assíncrona em duas áreas do aplicativo Doggy Directory: o dropdown de seleção que preenche a lista de raças e a chamada de API para recuperar imagens de cachorro quando uma busca é realizada.
Feche o arquivo src/mocks/mockFetch.js
. Agora que você entende como o método mockFetch
será usado em seus testes, você pode importá-lo para o seu arquivo de teste. A função mockFetch
será passada como argumento para o método mockImplementation
e será usada como uma implementação falsa da API fetch.
No arquivo src/App.test.js
, adicione as linhas destacadas de código para importar o método mockFetch
:
Este código irá configurar e desmontar a implementação simulada para que cada teste comece em uma base nivelada.
jest.spyOn(window, "fetch");
cria uma função simulada que irá rastrear chamadas ao método fetch
anexado à variável global window no DOM.
.mockImplementation(mockFetch);
aceita uma função que será usada para implementar o método de simulação. Como este comando substitui a implementação original de fetch
, ele será executado sempre que fetch
for chamado dentro do código do aplicativo.
Ao terminar, salve o arquivo src/App.test.js
.
Agora, ao visualizar seus testes no terminal, você receberá a seguinte saída:
Output console.error
Warning: An update to App inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
at App (/home/sammy/doggy-directory/src/App.js:5:31)
18 | })
19 | .then((json) => {
> 20 | setBreeds(Object.keys(json.message));
| ^
21 | });
22 | }, []);
23 |
...
PASS src/App.test.js
✓ renders the landing page (429 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.178 s, estimated 2 s
Ran all test suites related to changed files.
O aviso informa que uma atualização de estado ocorreu quando não era esperado. No entanto, a saída também indica que os testes simularam com sucesso o método fetch
.
Neste passo, você simulou o método fetch
e incorporou esse método a um conjunto de testes. Embora o teste esteja passando, ainda é necessário resolver o aviso.
Passo 4 — Corrigindo o Aviso act
Neste passo, você aprenderá como corrigir o aviso act
que surgiu após as alterações no Passo 3.
O aviso act
ocorre porque você simulou o método fetch
e, quando o componente é montado, ele faz uma chamada de API para obter a lista de raças. A lista de raças é armazenada em uma variável de estado que popula o elemento option
dentro do input select.
A imagem abaixo mostra como o input select fica após uma chamada de API bem-sucedida para popular a lista de raças:
O aviso é lançado porque o estado é definido após o bloco de teste terminar de renderizar o componente.
Para corrigir esse problema, adicione as modificações destacadas ao caso de teste em src/App.test.js
:
A palavra-chave async
informa ao Jest que o código assíncrono é executado como resultado da chamada de API que ocorre quando o componente é montado.
A new assertion with the findBy
query verifies that the document contains an option with the value of husky
. findBy
queries are used when you need to test asynchronous code that is dependent on something being in the DOM after a period of time. Because the findBy
query returns a promise that gets resolved when the requested element is found in the DOM, the await
keyword is used within the expect
method.
Ao terminar, salve as alterações feitas em src/App.test.js
.
Com as novas adições, você verá que o aviso act
não está mais presente nos seus testes:
Output PASS src/App.test.js
✓ renders the landing page (123 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 0.942 s, estimated 2 s
Ran all test suites related to changed files.
Watch Usage: Press w to show more.
Neste passo, você aprendeu como corrigir o aviso act
que pode ocorrer ao trabalhar com código assíncrono. Em seguida, você adicionará um segundo caso de teste para verificar as funcionalidades interativas da aplicação Doggy Directory.
Passo 5 — Testando a Funcionalidade de Busca
No último passo, você escreverá um novo caso de teste para verificar a funcionalidade de busca e exibição de imagens. Você utilizará uma variedade de consultas e métodos de API para alcançar a cobertura de teste adequada.
Retorne ao arquivo src/App.test.js
no seu editor. No topo do arquivo, importe a biblioteca complementar user-event
e o método assíncrono waitForElementToBeRemoved
para o arquivo de teste com os comandos destacados:
Você usará esses imports mais tarde nesta seção.
Após o método test()
inicial, adicione um novo bloco de teste assíncrono e renderize o componente App
com o seguinte bloco de código:
Com o componente renderizado, agora você pode adicionar funções que verificam os recursos interativos do aplicativo Doggy Directory.
Ainda no arquivo src/App.test.js
, adicione os blocos de código destacados dentro do segundo método test()
:
A seção destacada acima irá simular a seleção de uma raça de cachorro e verificar se o valor correto é exibido.
A consulta getByRole
pega o elemento selecionado e o atribui à variável select
.
Assim como você corrigiu o aviso de act
no Passo 4, use a consulta findByRole
para esperar que a opção cattledog
apareça no documento antes de prosseguir com mais assertivas.
O objeto userEvent
importado anteriormente irá simular interações comuns do usuário. Neste exemplo, o método selectOptions
seleciona a opção cattledog
pela qual você esperou na linha anterior.
A última linha assegura que a variável select
contenha o valor cattledog
selecionado acima.
A próxima seção que você irá adicionar ao bloco Javascript test()
irá iniciar a solicitação de busca para encontrar imagens de cachorros com base na raça selecionada e confirmar a presença de um estado de carregamento.
Adicione as linhas destacadas:
A consulta getByRole
localiza o botão de busca e o atribui à variável searchBtn
.
O matcher toBeDisabled
do jest-dom verificará se o botão de busca não está desativado quando uma seleção de raça é feita.
O método click
no objeto userEvent
simula clicar no botão de busca.
A função waitForElementToBeRemoved
assíncrona auxiliar importada anteriormente irá esperar pelo aparecimento e desaparecimento da mensagem Loading enquanto a chamada da API de pesquisa está em andamento. queryByText
dentro do retorno de chamada de waitForElementToBeRemoved
verifica a ausência de um elemento sem lançar um erro.
A imagem abaixo mostra o estado de carregamento que será exibido quando uma pesquisa estiver em andamento:
Em seguida, adicione o seguinte código Javascript para validar a imagem e a exibição do número de resultados:
A consulta getAllByRole
irá selecionar todas as imagens de cães e atribuí-las à variável dogImages
. A variante *AllBy*
da consulta retorna uma matriz contendo vários elementos que correspondem ao papel especificado. A variante *AllBy*
difere da variante ByRole
, que só pode retornar um único elemento.
A implementação simulada de fetch
continha dois URLs de imagem dentro da resposta. Com o comparador toHaveLength
do Jest, você pode verificar se há duas imagens exibidas.
A consulta getByText
verificará se a contagem de resultados apropriada aparece no canto direito.
Duas asserções usando os matchers toHaveAccessibleName
verificam se o texto alternativo apropriado está associado às imagens individuais.
A completed search displaying images of the dog based on the breed selected along with the number of results found will look like this:
Quando você combina todas as partes do novo código JavaScript, o arquivo App.test.js
ficará assim:
Salve as alterações feitas em src/App.test.js
.
Ao revisar seus testes, a saída final no terminal agora terá a seguinte saída:
Output PASS src/App.test.js
✓ renders the landing page (273 ms)
✓ should be able to search and display dog image results (123 ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 4.916 s
Ran all test suites related to changed files.
Watch Usage: Press w to show more.
Nesta etapa final, você adicionou um teste que verifica as funcionalidades de pesquisa, carregamento e exibição do aplicativo Doggy Directory. Com a asserção final escrita, agora você sabe que seu aplicativo funciona.
Conclusão
Ao longo deste tutorial, você escreveu casos de teste usando Jest, React Testing Library e os matchers jest-dom. Construindo incrementalmente, você escreveu testes baseados em como um usuário interage com a interface do usuário. Você também aprendeu as diferenças entre as consultas getBy*
, findBy*
e queryBy*
e como testar código assíncrono.
Para saber mais sobre os tópicos mencionados acima, dê uma olhada na documentação oficial do Jest, React Testing Library e jest-dom. Você também pode ler o Common Mistakes with React Testing Library de Kent C. Dodd para aprender as melhores práticas ao trabalhar com React Testing Library. Para mais informações sobre como usar testes de snapshot em um aplicativo React, confira How To Write Snapshot Tests.