Testes em ReactJS: o que é preciso para iniciar?

Há um tempo atrás eu ouvi a seguinte frase: 'Prazos são negociáveis, qualidade não!'. Isso me fez questionar algumas coisas, incluindo o que eu sabia sobre testes e a importância deles. Então, para sanar os questionamentos sobre como eles podem ser aplicados no ReactJS trouxe algumas informações sobre React, Jest e testing library.
Jessica Meira | 27 de abril de 2021

Antes de começar, caso você ainda não esteja familiarizado com o assunto de testes em ReactJS, há um artigo bem legal aqui no blog explicando sobre a importância dos testes unitários para garantir a qualidade do desenvolvimento de softwares. Outro artigo que eu gostaria de recomendar é: O que você precisa saber sobre JavaScript antes de começar no React (ainda mais se você tem dificuldade de entender a sintaxe de versões mais modernas de JavaScript).

Agora que você já está craque sobre a tecnologia que será abordada aqui, que é o React, vamos  partir para a compreensão dos testes no ReactJS e a maneira como podemos escrevê-los, entendendo alguns conceitos e suas estruturas.

ReactJS e testes

Em resumo, o React faz com que a árdua função de criação de views seja uma tarefa fácil. Ele irá atualizar e renderizar de forma eficiente apenas os componentes necessários na medida em que os dados mudam, fazendo com que seu código seja mais previsível e melhor aproveitado (ou reaproveitado).

A escolha da ferramenta precisa estar diretamente ligada à expectativa real. Usualmente as mais recomendadas são:

  • ** React testing library**: É um conjunto de utilitários que facilitam as consultas à DOM exatamente como um usuário faria, ou seja, é capaz de encontrar elementos de formulários, botões e links, entre outros. É importante se atentar que essa lib não é um test runner ou um framework, tanto que é altamente recomendável a utilização do Jest para isso. Mesmo assim, ainda há a opção de utilizá-la sem o jest. A própria documentação disponibiliza um tutorial de como isso é possível.

  • Jest: Esse framework foi criado pelo Facebook e tem como uma de suas principais qualidades a velocidade e facilidade em execução, já que ele visa trabalhar de uma forma simples, não há necessidade de configuração na maioria dos projetos, principalmente para o React. Ainda conta com a facilidade de lidar com snapshots e para melhorar o seu desempenho, cada teste é realizado de forma isolada.

Como funcionam as ferramentas de teste em ReactJS

As ferramentas de testes em ReactJS não irão necessariamente entender as regras ou os processos do seu projeto. O intuito de utilizar um teste é validar a funcionalidade e renderização dos componentes, se há determinado botão ou link na DOM e se o click está funcionando da maneira correta ou esperada.

Para um projeto pequeno talvez a ideia de teste seja um pouco trabalhosa e muitas vezes ignorada, mas na medida que o projeto cresce e novas features são desenvolvidas, você provavelmente agradecerá quem o iniciou, já que se torna praticamente inviável e inseguro criar os testes quando tudo já está tão avançado e há tantos processos novos para se testar.

Exemplos práticos de utilização

Para exemplificar os testes em ReactJS, vou seguir com a explicação criando um projeto quase do zero com comando npx create-react-app.

Normalmente, após a criação do projeto há uma organização de estruturas, mas como neste caso veremos apenas o conceito, iremos nos concentrar apenas no arquivo de teste dentro pasta src App.test.js

De começo precisamos entender o que está acontecendo nesse arquivo:

import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
 render(<App />);
 const linkElement = screen.getByText(/learn react/i);
 expect(linkElement).toBeInTheDocument();
});

A primeira coisa a notar nele é a importação da biblioteca @testing-library que é a responsável por interagir com a DOM. Por padrão vem a importação do render e do screen, que não vão exatamente apresentar a tela, mas sim capturar os elementos.

Depois, o Import do componente a ser testado, que no nosso arquivo de exemplo é o App.

A Sintaxe básica para esse arquivo de teste é:

test('My testname', () ={
 // steps to reproduce
});

O método test() - é o responsável por chamar os testes a serem executados e, dentro desse método, o primeiro argumento dará nome ao seu teste. Após, vem a função que contém as expectativas, ou seja, o que é pra acontecer e o que não é para acontecer dependendo do que você irá testar. Ainda nesse método há alguns outros argumentos, mas que não serão abordados por enquanto.

Dentro da função, será informado o passo a passo do que se deseja que aconteça.

  1. É renderizado o componente </App >

  2. Criado uma variável (linkElement)

  3. A variável recebe do método screen o elemento que possuir o texto learn react

  4. O esperado é que a variável esteja no documento.

Para rodar o teste utiliza-se o comando: Yarn test ou npm test. Como não foi alterado nenhuma informação, ao rodá-lo no terminal será apresentada uma tela de PASS, informando a quantidade e quais os testes presentes, quais deles passaram e falhas, caso existam.

Para mudar um pouco e entender um pequeno comportamento com os testes, vou adicionar um checkbox no meu app:

<div>
         <label htmlFor='checkbox'>Check</label>
         <input id='checkbox' type='checkbox'/>
       </div>

E no arquivo de teste posso inserir outra simulação, dessa vez com uma ação do usuário, como um click:

test('renders checkbox test', () => {
 render(<App />);
 userEvent.click(screen.getByText('Check'))
 expect(screen.getByLabelText('Check')).toBeChecked()
});

Para que esse teste rode com sucesso é necessário importar o userEvent:

import userEvent from '@testing-library/user-event';

O user-event é uma biblioteca complementar que nos permite executar ações mais avançadas. A testing library fornece o método fireEvent integrado que também nos permite executar algumas simulações, porém o mais recomendável para isso é o user-event.

Se o teste for executado novamente, os dois testes passarão.

Além desses pontos, algumas outras funcionalidades muito utilizadas em qualquer teste são as mocks e o snapshot.

Mocks

Mocks são utilizadas para simular chamadas reais, ou seja, permitem criar módulos, funções com retorno de dados que você possa controlar (normalmente, é preferível que sejam usados dados ‘falsos’ para testes a fim de evitar a lentidão e a inconstância) e simular uma dependência. Elas facilitam essas chamadas onde a implementação não seria viável, no caso dos testes, por exemplo.

Alguns modelos da sintaxe para a sua utilização:

//function
const mockFn = jest.fn();
mockFn();
expect(mockFn).toHaveBeenCalled();
//function 2
import getName from '../getusername'
const mockFname = {}
mockFname.getSomething = jest.fn()
mockFname.getSomething.mockReturnValue('Ateliware')
test('identificar nome do usuario, () => {
 expect(getName(mockFname)).toBe('Ateliware')
 expect(mockFname.getSomething.mock.calls.length).toBe(1)
})
//module
jest.mock('../moduleName', () => {
 return jest.fn(() => 9);
});
const moduleName = require('../moduleName');
moduleName(); // return '9';

Snapshots

Com testes de snapshots você é capaz de prevenir alterações indesejadas na UI do projeto. Então, podemos dizer que é uma maneira de prevenir bugs de layout, uma vez que você salva o componente em texto e informa que o resultado dessa renderização deve ser exatamente a mesma sempre.

Para salvar a renderização, primeiro há a importação do react-test-renderer. Caso você se depare com algum erro para identificar o módulo pode instalá-lo com npm install react-test-renderer.

import renderer from 'react-test-renderer';

O React test renderer é um pacote que fornece um renderizador de componentes que não depende da DOM. Ele auxilia na captura de snapshots.

const component = renderer.create(<App />);
 const tree = component.toJSON();
 expect(tree).toMatchSnapshot();

Note que eu estou passando o meu componente como argumento para o renderer e depois convertendo para json.

Os snapshots são geralmente salvos em uma pasta snapshots e o arquivo é similar a esse:

testes-em-reactjs.png

Por último, podemos abordar brevemente os testes assíncronos, que são uma prática muito comum. Nesses casos, o Jest precisa saber quando o código que está em teste é concluído antes de passar para outra avaliação. O Jest tem algumas maneiras de lidar com isso, vamos retratar duas aqui? A primeira delas é informando a palavra done.

Como no nosso exemplo estamos lidando com um callback, sempre que possível é interessante poder ver o log no caso de falha, então para isso precisaremos encapsular o expect em um bloco try e passar o erro no bloco catch para o done. Exemplo:

test('esperando pelo callback', done => {
 function callback(data) {
   try {
     expect(data).toBe('retorno');
     done();
} catch (error) {
     done(error);
   }
 }
 fetchData(callback);});

Outra maneira seria utilizando o async/await nos testes, que já são bem conhecidos no javascript. Para escrever um teste assíncrono, basta usar a palavra-chave async na frente da função, e aguardar os dados com o await. Exemplo:

//async - await
test('waiting return', async () => {
 const data = await fetchData();
 expect(data).toBe('Ateliware');
});

Agora que alguns dos conceitos mais importantes dos testes no react foram apresentados, já é possível realizar a construção de testes simples. Além dos métodos apresentados aqui no post, há um mundo muito mais profundo com diversas outras possibilidades dentro dos utilitários para testes.

Conclusão

A Elaboração de testes em ReactJS muitas vezes pode parecer complicada, mesmo que nos exemplos apresentados aqui todas as utilizações foram simples. Vale lembrar que cada projeto tem regras diferentes e maneiras diferentes para elaboração de testes. Alguns serão apenas testes de renderização correta dos componentes, outros terão os testes feitos a fim de validar a funcionalidade do componente.

Independente da usabilidade, os testes poderão validar e prevenir os erros na aplicação, garantindo, assim, a tranquilidade no decorrer do desenvolvimento, pois caso alguma funcionalidade apresente um comportamento inesperado após o desenvolvimento de uma nova feature, será mais fácil de identificar com os testes.

Por enquanto é isso! Espero que tenha sido útil aos seus novos conhecimentos e caso você ainda não tenha criado nenhum teste, nunca é tarde pra começar a garantir a qualidade do seu projeto.

Referências:

Jessica Meira
Software Engineer | Cursando pós-graduação em Inteligência Artificial, um pouco curiosa nas horas vagas. Mais de 15 anos na área de Tecnologia e apaixonada por música e instrumentos.