React Native: Criando testes automatizados
30.07
Continuando a série sobre React Native, neste artigo iremos cobrir como criar testes automatizados para nossa aplicação. Caso não esteja acompanhando, abaixo estão os links para os artigos anteriores:
Lá no primeiro artigo, iniciamos a nossa aplicação com o create-react-native-app, e com ele já temos o Jest configurado para rodar nossos testes automatizados, inclusive o CRNA, já criou um teste para nós, o App.test.js
. Mas antes de partimos para ação, vamos fazer uma breve contextualização sobre como iremos testar a nossa app.
A razão de testar, e no nosso caso, de forma automatizada, são muitas, as principais são:
No cenário do React Native, iremos utilizar o Jest, um test runner com esteróides, por já vir todo configurado com o coverage, mocks, assertions e testes de snapshot. Ou seja, é uma verdadeira bazuca, e para aplicações React e React Native faz sentido utilizar-la, pois iremos precisar dessas funcionalidades já providas por padrão.
Para rodar os testes, basta executar yarn test
ou npm test
, que irá rodar o comando presente no “scripts”=>”test” do nosso package.json. Como já temos um teste criado, você verá que o mesmo está quebrado (embora ele passe), com o erro: ReferenceError: XMLHttpRequest is not defined
Esse erro é mais do que esperado, pois os testes rodam com o Node.js, e não no browser, e o XMLHttpRequest, só existe por padrão no browser. E assim começa a nossa jornada no mundo dos testes automatizados.
Testar além de todos os beneficios que comentamos, é uma oportunidade de desacoplar o nosso código da infraestrutura, pois no contexto do teste, muitos detalhes precisam ser abstraídos, quando estamos fazendo testes de integração e unitários, afinal eles implicam em estarmos isolando o nosso código, para que eles não virem um teste de sistema. Aliás, se para você esses níveis de testes são confusos, segue uma breve descrição deles:
Voltando ao erro da falta do XMLHttpRequest, temos algumas opções para resolver:
global
fetch
pelo axios
, que também funciona com Node.js, e não representa uma grande mudança na nossa implementaçãoQual abordagem você escolheria? Acredito que a maioria iria para a 1, afinal estamos acostumados a instalar libs, e a mágia acontecer (isso é bom, mas também perigoso). A instalação de uma nova dependência, para substituir outra usada em produção, adiciona o risco de a implementação real, no caso o fetch
mudar, e os nossos testes não quebrarem, afinal não usam ele. Claro que isso é um cenário raro, pois o node-fetch
por exemplo, está de acordo com a especificação do fetch
, mas não deixa de ser um risco.
Já a segunda opção é insana (embora divertida), pois você terá que conhecer as implementações do fetch
e reimplementar mockando elas.
Agora a terceira opção, confesso que também acho meio “overkill”, trocar uma dependência, “só” para os testes? Sim, é isso que iremos fazer 🙂 – para evitar os riscos citados acima e seguir o “mandamento” não “mockaras” o código do próximo.
No fim, ainda iremos nos beneficiar de uma decisão que fizemos anteriormente, de isolar o uso do fetch
em um service que faz o wrapper dele. Com isso a mudança de código é mínima, e mostrada a seguir (não esqueça de instalar o axios, com yarn add axios
):
Agora ao rodar novamente os testes com yarn test
, o teste irá passar novamente, mas dessa vez sem erros no console. E provavelmente você deve está se perguntando: como que o teste passava, sendo que estava com um erro? O problema está na fragilidade do assert que o CRNA gerou para nós, que como um gerador em si não é tão problemático, o problema é seguirmos o mesmo padrão nos nossos testes.
O teste gerado foi o seguinte:
const rendered = renderer.create(<App />).toJSON(); expect(rendered).toBeTruthy();
Na primeira linha, está sendo usado o react-test-render
, para renderizar nossa app sem o ambiente nativo, retornando a app num objeto JavaScript, e fazemos ainda o parse para JSON dele. Já na segunda linha, é que está o grande problema, a verificação/assertion não é determinística, afinal testar usando toBeTruthy, irá retornar true sempre que tivermos algum valor no rendered
(pode ser tanto true, uma string, objeto, número, etc), ainda mais que estamos testando a renderização do App em si, e não a existência de algum componente dentro dele.
Nosso primeiro teste então, será melhorar esse teste, que no final atende o que ele testa “renders without crashing”, porém queremos ser mais preciso na validação.
No teste acima, temos algumas melhorias em cima do anterior:
toBeTruthy
, estamos fazendo a assertion em cima do componente principal para o teste, que é neste caso o ActivityIndicatorRendered
, que se renderizado, é sinal que o loading está sendo apresentadoPorém, este teste ainda iria passar, no cenário anterior, que estávamos usando o fetch, isso pois o fetch faz parte um processo assíncrono, e o nosso teste é síncrono e não depende do fetch ocorrer com sucesso, pois estamos testando um momento antes.
Caso o nosso componente de loading não fosse apenas uma gif, poderíamos fazer a assert, baseada no seu conteúdo, ao invés, de apenas na existência do componente em si. Porém esse não é a abordagem correta, pois estaríamos trazendo um detalhe do TeamsList, para o teste do App. Portanto sempre tenha em mente, que os testes não podem ser em cima de efeitos de outro código, além do que estamos testando.
Chegou a hora de criarmos mais um teste: um teste assíncrono
Primeiro vamos criar uma nova pasta, chamada test
e dentro dela 3 novas pastas para cada nível de teste:
Vamos mover o teste que criamos para a pasta e2e: mv App.test.js test/e2e/
Além disso, é preciso altera os seguintes imports:
import TeamsList from '../../src/components/TeamsList'; import App from '../../App';
Se você já viu outros projetos em React/React Native, pode estar estrando a separação do teste da implementação, por geralmente os próprios geradores criarem o teste junto a implementação, como é o caso do próprio App.test.js.
Estamos fazendo uso por dois motivos principais:
Agora crie o arquivo test/integration/services/httpRequest.test.js
Ainda estamos utilizando o sufixo
.test
, pois caso no futuro desejarmos organizar os testes de outra forma (por exemplo, junto a implementação), a mudança seja menor.
Sobre o teste é importante notar alguns pontos:
axios
, por isso não mockamos ele (e também devido ao mandamento que falei anteriormente)given
, when
e then
mais com o intuito de facilitar a leitura do teste, pode parecer descenessário pois estamos com o código e teste fresco na cabeça, mas daqui 6 meses, ou para uma pessoa nova no projeto, irá ajudardescribe
para contextualizar o teste, e ele ajudar na visualização dos resultados ao rodar o yarn test
Durante o teste, notei um bug no tratamento de erro no src/services/httpRequest.js
, que estava retornando o objeto direto de erro do axios, e não um parse dele. O código abaixo corrige o bug:
Com o teste pronto e a correção feita, basta rodar yarn test
para rodar os testes.
Assim finalizamos mais um artigo da série, no próximo iremos continuar testando nossa aplicação, pois se você observar (rodando yarn test --coverage --collectCoverageFrom=src/**/*.{js,jsx}
e abrindo o arquivo gerado em coverage/lcov-report/index.html
), faltam alguns testes para garantir o correto funcionamento da aplicação, e por assim uma boa cobertura de testes.
Até a próxima!
O código apresentado está disponível no Github
Na Vizir Talks #32, o Assis Neto falou sobre suas impressões do Deno.JS, veja o vídeo...
💡 #aprendi é um canal interno onde os Vizires compartilham aprendizados do dia a dia...
💡 #aprendi é um canal interno onde os Vizires compartilham aprendizados do dia a dia...
💡 #aprendi é um canal interno onde os Vizires compartilham aprendizados do dia a dia...
💡 #aprendi é um canal interno onde os Vizires compartilham aprendizados do dia a dia e...