Testando código com Reactjs: minha abordagem

Marcos Pereira
5 min readAug 10, 2022

--

Quando falamos em testes unitários, logo pensamos em algo que precisa ser simples e servir como um auxiliar no processo de desenvolvimento. O problema é que nem sempre as coisas saem dessa forma.

Pensando nisso vamos ver um pequeno resumo de como eu costumo testar Reactjs hoje em dia. O objetivo é criar uma metodologia que padronize ao máximo as rotinas de testes para que estes sejam aliados para escrever código limpo, mínimo e bem estruturado, testando componentes e chamadas http.

Melhorando a “testabilidade” do código

Um dos pontos mais importantes para que os testes sejam simples de desenvolver, é que o próprio código seja fácil de testar.

Neste tutorial, vamos utilizar a testing library . Olhando para os seletores que ela fornece, temos um que foi pensado para ser o mais genérico possível: o testid. A semântica por trás deste seletor deixa bem claro que trata-se de algo utilizado nos testes. Dessa forma o seu uso deixa claro, também, que alterações neste atributo podem impactar nos testes, evitando falhas indesejadas ou a necessidade de comunicação desnecessária entre membros do time.

Para utilizar o testid como seletor, basta adicionar o atributo data-testid nas tags dos elementos que desejamos selecionar para o teste. Dito isto, vamos começar nosso tutorial criando um component que implementa um input com a “testabilidade” em mente.

Um input simples que responde ao evento onChange e recebe um id, um label e uma mensagem de erro via props. Note que foram adicionados os atributos data-testid tanto para o input quando para a div que contém a mensagem de erro. A última derivada do id atribuído ao input.

Agora, partimos para o teste do comportamento do nosso input.

Implementado os testes

Queremos testar se a mensagem de erro é apresentada quando passada para o input. Vamos supor que, em nosso cenário, gostaríamos que uma mensagem de erro seja apresentada caso o número de caracteres do input seja inferior a 8. Essa validação deve ser realizada a cada mudança no input.

Vamos implementar um componente que utilize nosso input fornecendo as validações necessárias.

Agora criamos nosso arquivo de testes para o componente TestForm. É comum e convencional criá-lo no mesmo diretório do arquivo do componente e nomeá-lo com o mesmo nome acrescido de “.test.js”.

Agora já podemos ver as peças se encaixando. O primeiro passo para o teste é renderizar o componente que queremos testar. Quanto mais isolado o escopo melhor. No nosso caso, renderizamos o componente TestForm inteiro utilizando a função render.

Feito isso, o componente está disponível para que possamos interagir com ele. Para selecionar elementos, utilizamos o método getByTestId do objeto screen da testing-library. Este método seleciona os elementos através do atributo data-testid que definimos anteriormente.

O terceiro ponto importante na implementação dos testes é poder simular eventos sobre os componentes testados. Isso pode ser realizado através do objeto fireEvent da testing-library. Com ele podemos lançar qualquer evento suportado por um dado elemento. Em nosso exemplo, causamos a chamada de nosso handler lançando um evento change sobre o input.

Vamos adicionar um novo teste que verifica se a mensagem de erro desaparece ao fornecer um valor válido para o input:

E é isso! Com pequenas variações é possível testar virtualmente qualquer comportamento desejado.

Testando chamadas http

A interação da aplicação com o backend também pode ser testada através de mocks. É bastante comum utilizar fetch para as chamadas http, assim selecionei a biblioteca jest-fetch-mock que tem uma api fácil de usar e funciona muito bem.

Primeiro, vamos preparar o nosso TestForm para que possamos ilustrar um teste que envolva uma chamada http. Cabe salientar que estamos desenvolvendo o componente antes de criar os testes, pois o foco é ilustrar as ferramentas para o teste, porém, a mesma abordagem pode ser utilizada para conduzir a criação de código através de TDD.

Note que adicionamos o atributo data-testid aos componentes de interesse para o teste.

O handler faz um post para uma api que retorna um echo do que for enviado. O endpoint pouco importa pois a chamada será interceptada por um mock no teste.

Vamos adicionar um teste que simula uma chamada com retorno de erro:

O objeto fetchMock é exportado pelo modulo “jest-fetch-mock”. Tudo que precisa ser feito é chamar o método enableMocks e, em seguida, setar a resposta que o mock deve fornecer para a nossa chamada. Há diversos outros métodos para setar respostas que podem atender a casos de uso mais complexos. Em nosso exemplo, simular uma resposta já é o suficiente. Ao setar o status 500 com o método mockResponseOnce, a próxima chamada ao fetch retornará o status desejado ao invés de chamar o endpoint real. O primeiro parâmetro do método permite setar um valor que será retornado no body da resposta, caso isso seja útil para o teste.

Um ponto de atenção é que, ao envolver chamadas para a web, estamos interagindo com funções assíncronas, dessa forma a função que implementa o teste precisa ser assíncrona também.

A chamada waitFor nas ultimas linhas serve para evitar erros ao testar o conteúdo do elemento de interesse. Fora deste bloco assíncrono, existem situações onde ocorrem falhas relacionadas à rejeições não tratadas em blocos assíncronos. Utilizando este “wrapper” o teste prossegue sem erros.

Por fim, vamos adicionar um erro simulando uma resposta de sucesso:

Nada de novo aqui, com pequenas alterações, testamos outro comportamento.

Vale lembrar que o jest fornece uma série de ferramentas que podem auxiliar a estruturar melhor o código dos testes mas que fogem do escopo deste tutorial.

Para finalizar, cabe uma reflexão. Muitas vezes responder a pergunta “o que testar?” acaba sendo mais importante do que a implementação dos testes em si. Testar muito minuciosamente, investindo tempo em detalhes que não precisariam ser testados, ou que agregam pouco valor ao objetivo de manter uma base de testes, pode acabar trazendo efeitos negativos tanto na disposição do time para incrementar a base de testes, quanto nos impactos negativos nos prazos das suas entregas. Equilíbrio é tudo.

Então é isso! O que me resta é dar um tapa no visual do exemplo utilizado e deixar disponível para quem quiser dar uma olhada no github:

Obrigado pela leitura e até a próxima!

--

--

Marcos Pereira

Software developer in constant evolution. Professional father. Mediocre musician =p