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.
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.
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.
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.
É renderizado o componente </App >
Criado uma variável (linkElement)
A variável recebe do método screen o elemento que possuir o texto learn react
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 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';
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:
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.
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.