Testes Eficientes Usando React Testing Library
Testando e tornando seus componentes React indestrutíveis, da forma certa.
A partir do momento que o React ganhou popularidade, muitas pessoas já pensavam na melhor forma de poder testar componentes para garantir que os mesmos fizessem o que era esperado. A ideia é simples: fazer testes que garantem que aquele botão faça algo quando apertado, que o seu <Dropdown />
mostre uma lista de opções quando acionado, e assim vai.
O que não foi tão simples nessa história toda foi chegar num consenso de qual é a melhor forma de testar isso. Afinal, se tem uma coisa que engenheiros, cientistas da computação e profissionais em tecnologia em geral fazem bem, é saber se quebrar na porrada proverbial brigando por um ponto de vista.
A discussão era em torno exatamente como testar os componentes. Uma das bibliotecas a ganhar muita atenção logo no começo foi a Enzyme. Criada pelo Airbnb, a premissa do Enzyme é facilitar o acesso a valores de um componente renderizado, podendo verificar e testar esses valores para ver se batem com os valores esperados pelo desenvolvedor.
No teste acima, usamos o shallow
do Enzyme para renderizar (em memória) o componente e, usando um helper .state()
, pegamos um valor de dentro do estado do componente e testamos se o valor equivale ao que esperamos, nesse caso, se é true
Testes Testando Errado?
O que acabamos de ver é um teste, não é? Formalmente, com certeza. Mas a discussão que gera certa controvérsia é que um teste desse tipo é um teste que testa o componente de forma errada. A razão disso é que veja bem o que está sendo testado — nesse caso, o state
do componente (que é um valor interno e privado do componente) averiguando se o estado do componente tem um certo valor. O problema disso (e eu concordo plenamente) é que testar valores desse tipo testam muito mais detalhes de implementação do que o funcionamento correto do componente.
Se eu testo que um componente começa com um valor interno em true
e que, ao ser clicado, passa esse valor para false
, eu estou efetivamente estando o componente? Ou seja, estamos garantindo que o componente FAZ o que ele precisa fazer ou estamos testando se o desenvolvedor programou corretamente o componente? Nesse tipo de teste, acaba sendo o último — o teste passa a ser uma verificação de programação invés de testar o que efetivamente faz.
Teste o que um componente faz e não como ele foi programado
Com esse conceito em mente, o React Testing Library (RTL) foi criado. Inventado pelo nobre Kent Dodds, o RTL força (na amizade, de forma delicada) você a escrever testes que verificam o uso e a funcionalidade de um componente, imitando justamente como um usuário interfacearia com o mesmo. Dessa forma, o teste testa certo 👌
Testes Testando Certo!
Vamos criar nosso componente (imaginário) a ser testado, o <Dropdown />
, usando nossa lista de requisitos abaixo:
- Começa fechado
- Ao ser clicado enquanto fechado, abre e mostra a lista de opções que recebe como parâmetro
- Quando uma opção é clicada, fecha a lista e indica a opção selecionada
Mesmo não sendo muito complexo, ter uma lista formal em mente do que você espera de algum pedaço de software e sempre útil. Com isso em mãos, vamos começar a ver como podemos testar e garantir que nosso componente faz o que esperamos que ele faça, criando os testes baseados nessas funcionalidades que destacamos (⚠️ Não vou explicar como instalar o RTL nesse post porque depende muito do ambiente em que está trabalhando — assista o vídeo no meu canal do YouTube para ver um exemplo funcional)
Repare que estou incluindo uma série de bibliotecas nos import
s. Dessa lista, destacamos:
@testing-library/jest-dom
: adiciona métodos de verificação ao Jest, úteis para testes de componentes, como o.toBeInTheDocument()
e.toHaveValue()
;@testing-library/dom
: entre outras coisas, te fornece oscreen
, que é como um browser em memória, que mantém o componente renderizado e é de onde vamos puxar os componentes baseados nos queries do RTL;@testing-library/react
: adiciona a capacidade de renderizar componentes React no mundo do RTL;@testing-library/user-event
: fornece ações de usuário para interagir com os componentes renderizados.
Com toda essa parafernália instalada e incluída, podemos começar a escrever nossos testes, focando em cada um dos testes por vez:
Normalmente o primeiro passo do teste de componente é renderizar o componente para que RTL possa fazer um query em cima dele depois. Aqui estamos renderizando nosso Dropdown
com algumas opções de escolha de Pokémon (Por que Pokémon? Poké não? 🥁), o título do dropdown quando ele estiver fechado e a função que vai ser chamada quando uma das opções é selecionada.
Repare que passamos uma função vazia para
onSelect
e é normal ver valores “vazios” assim em testes. Isso é feito para explicitar que aquela prop não será testada e seu valor não importa para o respectivo teste
O primeiro teste é para garantir que o Dropdown
comece fechado, sem mostrar as suas opções. A primeira pergunta portanto a se fazer é a seguinte: como testamos que algo não é visível?
Uma das formas é verificar se um valor interno do Dropdown
(e.g. um valor de estado isOpen
) é false
, garantindo que booleanamente temos o valor correto para essa variável. Mas isso implica no problema que levantamos antes: não teste implementação, teste funcionalidade. E a melhor forma de testar funcionalidade é imitar o que seu usuário vê e faz — e é justamente nesse ponto que o RTL brilha.
Imitar seu usuário nesse caso é verificar que algo não pode ser visto e o RTL (junto com o @testing-library/jest-dom
) fornece formas eficientes de testar isso:
Depois de renderizar o componente, usamos o screen
, um utilitário que simula como se fosse a tela de um usuário, que fornece vários métodos para podermos buscar “coisas” dentro do que foi renderizado. Essas coisas podem ser inputs, labels, botões, checkboxes e qualquer outro tipo de elemento que o componente pode gerar. As opções para buscar esses elementos também são diversas e o RTL incentiva que a busca deve ser feita da forma mais “usuário” possível, ou seja, que se busque como um usuário buscaria aquele elemento.
No nosso exemplo, usamos o texto da lista de opções do Dropdown
mesmo, que seria algo bem visual que nosso usuário viria. Isso também poderia ser por role
(algo como screen.getByRole('button')
) dependendo do nosso componente (a sequência de prioridade de busca pode ser vista aqui, junto com uma explicação). No nosso teste, foi verificado que nenhum elemento com o nome dos Pokémão que passamos estava presente no documento e que, se estivesse, falharia nosso teste. As opções de asserção são muitas, e uma lista gigante pode ser encontrada aqui.
Agora que você entendeu a lógica do que deve ser feito para fazer um teste da forma correta, pause esse post (tira o dedo desse scroll!) e elabore mentalmente como os próximos dois testes devem ser feitos. O que falta testar então é:
- Ao ser clicado enquanto fechado, abre e mostra a lista de opções que recebe como parâmetro
- Quando uma opção é clicada, fecha a lista e indica a opção selecionada
Pensa bem — não vai demorar mais que 2 minutos, beleza?
…
Pensou?
Maravilha?
Pensou bem?
Ótimo! (ou você mentiu mas aí vai na tua consciência)
Bora lá.
Mais Testes Testando Certo
Nosso segundo teste aborda a questão de uma ação do usuário e responder a essa ação. Para isso, temos a maravilhosa biblioteca do @testing-library/user-event
, que simula lindamente a interação de um usuário com o browser. Alguns dos métodos que ela expõe são o click
, o clear
e o type
, dentre outros, que podem ser vistos aqui. Então simbora para nosso teste!
Logo de cara, repare que passamos nossas opções e título para variáveis, facilitando o reuso deles e, mais importantemente, mostrando a relação explícita entre aquele valor e o valor testado. Sempre que puder mostrar que intencionalmente verificou um valor, faça isso.
Em seguida, renderizamos o componente e checamos novamente que nenhuma opção aparece (não tem problema repetir esse pedaço do teste porque faz sentido semântico ao teste atual também). Agora o getByRole
vai servir para pegar uma referência ao elemento principal do Dropdown
e vamos usar o userEvent
para disparar uma clicada nesse botão (é um componente imaginário, então tô imaginando que o elemento principal é um botão 🤷♀️). Uma vez clicado, testamos para garantir que as opções do dropdown vão estar presentes na tela (por exemplo, esse getByTest
poderia ser um screen.getByRole(‘menuitem’, { name: options[0] })
caso as opções fossem ítens de um menu). De qualquer maneira, garantimos que eles estão presentes e visíveis 👀
E para nosso último teste, nada especial — vamos agora garantir que “algo aconteça” e que somos sinalizados que esse “algo” aconteceu.
E voilá: mágica. Tudo começa igual mas repare agora que estamos passando uma função mockada para o onSelect
, justamente porque queremos verificar se essa função vai ser chamada quando deve ser chamada. Renderizamos, abrimos e selecionamos uma opção (a melhor delas, Bulbasaur
), e verificamos que o menu fechou depois de opção selecionada. Inclusive foi verificado que a função mockonSelect
foi chamada para garantir que o componente sinaliza o componente-pai que uma opção foi selecionada. Você pode me questionar: “Ricardo, seu cusão, você não disse que testar implementação é errado?” e eu te respondo — Sim, mas nesse caso defendo por três razões: 1/ é uma funcionalidade essencial desse componente que, se não funcionar, inutiliza o componente 2/ não entra em muitos detalhes de implementação, apenas que “algo” acontece e 3/ Nunca leve regras muito a sério — se algo parece importante para você, vai lá e faça, caraio.
Conclusão
Agora que você sabe testar corretamente componentes, não quero ver mais aplicação quebrando por aí. Se você curtiu esse post, me faça o pequeno favor de se inscrever no meu canal do YouTube, onde tenho um vídeo abordando justamente esse assunto em detalhes ;)
TLDR;
- React Testing Library (RTL) é uma biblioteca de teste para React que facilita e incentiva tests feitos da forma correta — teste seus componentes e como seu usuário os usaria;
- Teste funcionalidade e não valores — imite o que o seu usuário faria e não o que você espera do desenvolvedor;
- Utilize um test runner qualquer como o Jest e rode seus testes normalmente, junto com as capacidades adicionais de query e testes do RTL
Espero que esse post tenha te ajudado!
Ficou com dúvida ou quer mandar uma real? Deixa nos comentários!
Ah, e me acompanhe também no YouTube: https://bit.ly/3q0TIAU ✌!