Testando componentes React
Quando comecei a me aventurar no React, por não ter um background muito forte com o javascript moderno (digo moderno, porque quando comecei a trabalhar com web, o ES5 tinha acabado de sair do forno), tomei um certo dano para tentar aplicar os mesmos tipos de testes que faço diariamente no backend, principalmente quando se trata de TDD.
Depois de aprender um pouco mais sobre o React (e também Redux), comecei a estudar alguns frameworks de testes, o React já te dá algumas ferramentas para isso, mas na própria documentação recomenda-se o enzyme (http://airbnb.io/enzyme/).
Primeiros passos
Para os primeiros testes de componentes de tela, comecei a usar o enzyme, por ter uma documentação completa e uma API (ao meu ver) muito boa.
Para os exemplos neste post, utilizarei um projeto pessoal que consome a API da Marvel, você pode ver mais detalhes em: https://github.com/hudolfhess/MarvelHeroes (o projeto é para estudos, e deverá mudar com o tempo, mas os exemplos ainda continuarão aqui).
Serão dois exemplos de componentes de tela: SearchBar (que é apenas um input para fazer uma busca) e ListHeroes (que lista os heróis retornados da API).
Exemplo 1 — SearchBar.jsx
Bom, até o momento esse é o código final do componente SearchBar, onde ele basicamente exibe um input e recebe um método para controlar o onChange do input. Podemos notar também, que ele possui um controle para que o onChange seja chamado apenas após 300ms depois de parar de digitar, isso para evitar que muitas requests sejam efetuadas na API enquanto o usuário está escrevendo o termo a ser buscado.
Mas e agora? Como podemos testar esses comportamentos?
Bom, vamos listar o que queremos testar aqui:
- O componente deve renderizar sem erros;
- O valor da prop
defaultValue
deve ser iniciado no state do componente; - O valor do state
search
deve ser igual ao valor do input da busca; - Ao alterar o valor do input, deve ser disparado o evento passado pela prop
onChange
; - Ao alterar várias vezes o valor do input, o evento passado pela prop
onChange
deve ser chamado apenas uma vez com o último valor passado.
Por enquanto essa é a nossa lista de testes. Vamos lá, mãos a obra.
Criando arquivo de testes
Você pode criar o arquivo dentro da mesma pasta do componente, eu prefiro deixar separado, digamos que o componente fique em src/components/SearchBar/
, o meu teste ficará em src/__tests__/components/SearchBar
.
Teste 1 — Garantindo que o componente seja renderizado sem erros
Escolhido o local, crie o arquivo com o nome SearchBar.spec.js
. Com o arquivo criado, podemos criar o nosso primeiro teste, ele ficará assim:
Como podemos ver, utilizei o enzyme para renderizar o meu componente, mais especificadamente o método render do enzyme, que basicamente irá renderizar o componente em um HTML estático. Além do render, temos o shallow e o mount, falaremos deles mais adiante, mas por ora, utilizaremos o render, que das 3 opções que temos, é o mais leve por não dar muito poder de fogo, o que não precisamos agora.
Para executar o teste, basta executar o seguinte comando no terminal:
npm test components/SearchBar/
O resultado deve ser esse:
Testes 2 e 3 — Testando o estado inicial do componente
Os próximos dois testes são bem parecidos, então farei numa tacada só aqui:
Nestes dois testes, utilizei o shallow, que é um intermediário entre o render e o mount, ele não é tão poderoso quanto o mount, mas para o que eu preciso é o suficiente. Diferente do render, o shallow me dá acesso ao estado do componente, podendo inclusive altera-lo, ou até buscar componentes internos e saber qual o estado do mesmo.
Teste 4 — Garantindo que a busca seja efetuada
Nosso próximo teste será para validar se o onChange do input está chamando nossa prop onChange
passada como parâmetro.
Neste teste, tivemos uns recursos a mais, isso se justifica pelo fato de usarmos o window.setTimeout
. Felizmente, a lib de testes que estamos usando, o jest
, possui algumas mágicas para facilitar os testes nestes casos.
Como podemos ver, na linha 2 chamamos jest.useFakeTimers()
, que basicamente irá substituir as implementações dos métodos: setTimeout, clearTimeout, setInterval, clearInterval. Que caso fossem necessários executar no nosso teste precisaríamos forçar um sleep de 300ms (que é bem ruim).
Logo depois, na linha 12, precisamos chamar o jest.runOnlyPendingTimers()
que como o nome diz, irá executar as funções pendentes (respeitando o clearInterval/clearTimeout). Para mais detalhes dos Timer Mocks do jest, acesse https://facebook.github.io/jest/docs/timer-mocks.html.
Último teste — apenas uma busca por vez
E por último, precisamos de um teste que garanta que a função passada na prop onChange
seja executada apenas quando o usuário concluir de digitar o que estiver buscando.
O teste é bem parecido com o anterior, com a diferença que simulamos diversas vezes o onChange do input, mas como podemos ver, após executar os timers pendentes, apenas o último foi executado.
Considerações sobre o primeiro exemplo
Este foi o primeiro exemplo, um simples componente com um input que dispara um evento toda vez que o valor for alterado, conseguimos criar 5 testes cobrindo os principais cenários.
Você pode ver o arquivo de testes completo neste link.
Exemplo 2 — ListHeroes.jsx
Este componente tem como objetivo listar todos os heróis buscados na API da Marvel. Ele usa a SearchBar para gerenciar as consultas e também recebe um gateway por dependência.
Teste 1 — Garantindo que o componente seja renderizado sem erros
Bom, vamos lá, criamos o arquivo de testes seguindo o mesmo padrão do anterior, e fazemos nosso primeiro teste, apenas para garantir que o mesmo foi renderizado corretamente.
Teste 2 — componentDidMount
Agora, precisamos garantir que quando o componente for iniciado, ou o evento componentDidMount
for chamado, ele busque todos os heróis conforme o estado inicial da busca.
Para esse teste, precisamos de um setUp um pouco maior, criando um mock para o gateway a ser passado para o método. Repare, que neste teste utilizei o mount
ao invés do shallow ou render, e o motivo para ter usado o mount, é que apenas ele passa pelo ciclo componentDidMount
do componente, que é o que queremos testar aqui.
Testes 3 e 4— Sempre que o valor da busca for alterado, uma nova busca deve ser efetuada
Além de garantir que ao passar pelo componentDidMount
, precisamos garantir que sempre que o valor de search
no estado do componente for atualizado, e apenas quando for um novo valor, ele deve buscar os dados novamente na API da Marvel.
Assim garantimos que sempre que for salvo um novo termo de busca, o gateway será chamado e salvará o resultado no estado do componente.
Teste 5 — Simulando uma busca no SearchBar
Precisamos garantir também, que quando algo for alterado no componente de busca, deve salvar o novo termo no estado do componente para que seja efetuada uma nova busca na API.
Como já garantimos em outro teste que sempre que o valor de search
for alterado no estado do componente irá efetuar uma nova busca na API, não precisamos dar o assert aqui que o método do gateway foi chamado.
Último teste — Renderizando os heróis
Agora podemos ir para o último teste:
Aqui, apenas garantimos que os heróis retornados na busca serão renderizados no nosso componente.
Você pode acessar a versão final do teste através deste link.
Conclusão
Como podemos ver, testar componentes React é relativamente fácil, as documentações, tanto do jest quanto do enzyme são bem completas, além da comunidade ser bem ativa também.
No próximo post, pretendo adicionar o Redux na aplicação e assim, vermos também os efeitos que teremos sobre os testes, tanto de componente de tela quanto os novos que surgirão.