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:

No último artigo iniciamos os testes automatizados, e vimos por meio do relatório de cobertura, que há vários códigos não testados. Neste artigo iremos justamente criar os testes que faltam, e durante a criação entender melhor o que devemos testar, o que não devemos e o principal: como criar bons testes, indo além do teste de snapshot.

Testando os componentes

Vamos começar os testes de forma bottom-up, começando pelo TeamDetails, que se encontra abaixo:

Analisando o código acima com “chapéu” de tester/usuário, identificamos os seguintes cenários de testes:

  1. Renderizar corretamente os detalhes da seleção, com foto, estatísticas e jogadores
  2. Renderizar sem quebrar, quando os dados da seleção estão vazios
  3. Renderizar sem quebrar, quando a seleção não tem jogadores

O primeiro cenário é o mais básico, é o que chegamos geralmente quando estamos pensando como desenvolvedor, agora os outros dois são os chamados cenários de exceção, pois representam cenários que fogem ao que foi pensado inicialmente, e podem ocorrer com a aplicação em produção, pois a API pode de repente ter alguma falha, e nossa aplicação precisa estar minimamente preparada para não quebrar.

Crie um arquivo novo em test/unit/components/TeamDetails.test.js, com o seguinte teste inicial cobrindo o primeiro cenário:

No teste acima estamos utilizando uma fixture que foi criada em test/fixtures/teams/brazil.json. Já o nosso teste é bem simples:

  • Estamos renderizando o nosso componente passando o team que vem da nossa fixture, utilizando o react-test-renderer
  • Fazemos a assertion utilizando a funcionalidade do Jest de comparar o resultado obtido com o snapshot salvo. Que irá passar sempre que o resultado for o mesmo do gerado na primeira execução (ou atualizado depois com o -u).

Este teste é melhor que nada, porém ainda está longe de ser um bom teste. Isso devido aos seguintes motivos:

  • O teste de snapshot, é um teste frágil, pois qualquer mudança de apresentação (ex: vamos adicionar uma estatística nova), irá quebrar o teste, afinal é um teste de view, e tais testes são os mais frágeis. Esse é um problema que ao escolher usar o teste de snapshot, temos que aceitar
  • É de extrema importância conferir o snapshot criado (no teste acima, foi gerado em test/unit/components/__snapshots__/TeamDetails.test.js.snap), se ele bate com o que você está esperando. Isso é óbvio, porém num cenário de uma grande aplicação, prazos apertados, etc infelizmente pode acontecer do desenvolvedor simplesmente confiar, e a snapshot não ter sido gerada conforme o esperado (pense que snapshot seria como você verificar no navegador o resultado, e você não verifica)
  • Nossa assertion é muito grande (a view inteira), assim, caso quebre fica difícil de identificar, se foi apenas um componente que trocamos, ou se realmente quebrou algo

Devido as características acima, iremos refatorar nosso teste, que ficará do seguinte jeito:

Com esta nova versão do teste, estamos garantindo explicitamente que alguns dados estejam presentes no componente renderizado. Com isso melhoramos a nossa verificação, deixando mais precisa e também facilitamos a manutenção do teste, pois caso por exemplo o ParallaxScrollView falhe, a imagem não será renderizada, e teremos o feedback que a imagem não foi renderizada, fora que estamos deixando claro qual o comportamento esperado do teste, algo que apenas com o snapshot não teríamos. Note que a assertion usando o snapshot é a última, justamente porquê ela é a mais frágil, e assim se quebrar, não irá deixar de executar as outras assertions antes.

Agora que temos nosso primeiro teste de componente, é bom entendermos o que ele garante, e o que não garante:

  • Garante
    • que a lógica interna do compoennte está sendo testada
    • que os diferentes componentes que fazem parte da nossa view estão sendo renderizados
  • Não garante
    • que a renderização no dispositivo está conforme o esperado
    • que não há erros de layout

Portanto, nosso teste embora seja praticamente um teste de UI, ele ainda não é um teste de UI completo, justamente por usarmos o react-test-renderer. Mas por outro lado, ele já nos garante algumas aspectos do nosso componente, portanto faz sentido criarmos, mesmo eles sendo frágeis.

Configurando o Jest

Ao rodar o novo teste o seguinte warning, deve ter aparecido pra ti: Animated: `useNativeDriver` is not supported because the native animated module is missing.

Para corrigir faça os seguintes passos:

  • Crie o arquivo test/jestSetup.js, com o seguinte código dentro: jest.mock('NativeAnimatedHelper');
  • No package.json, coloque na seção jest, a nova configuração abaixo:
"setupFiles": [
  "./test/jestSetup"
]

Testando cenários de exceção

Vamos criar os dois testes que faltam para o nosso componente, ficando nosso arquivo test/unit/components/TeamDetails.test.js da seguinte maneira:

Ao rodar, você irá notar que nossa implementação não está pronta para o cenário do team ser undefined, para resolver isso, iremos atualizar o nosso PropTypes (que inclusive estava desatualizado), para adicionar um valor padrão:

 

Note que fizemos uma pequena refatoração, para não usar snake_case, no nosso código JS, utilizando a lib humps. Essa mudança foi feita tanto no teste acima e na implementação já apresentados, como no src/services/httpRequest.js.

Agora nossos testes irão passar. Algo a ser notar nos testes, é que estamos fazendo um “teste de sanidade”, verificando que a imagem da seleção e jogadores não são apresentadas. Porquê é algo barato de se verificar, e verifica se não estamos testando errado, ou vazando o estado de um teste para outro. Note também que adicionamos os dois novos testes em um novo describe, isso é importante para melhorar a leitura e manutenção dos testes.

Os testes são uma excelente ferramenta para criarmos soluções mais robustas, pois é barato e simples simular cenários, que manualmente seriam mais custosos. Como vimos com o cenário de não recebermos os dados da seleção, onde nossa página estava simplesmente quebrando. A solução dada foi a mais simples, mas já torna a aplicação preparada para tal cenário, e ainda temos a garantia disso através dos testes automatizados.

Testando os demais componentes

A suíte de testes para o componente TeamItem, fica da seguinte forma.

  • Aqui tivemos que mockar, o navigation, pois é um componente nativo e não conseguimos testar usando o Jest. O importante a notar, é que estamos fazendo a assertion do parâmetros recebidos também, e não apenas se a função foi chamada – para especificar mais o teste, já que não conseguimos testar se a navegação em si ocorreu
  • Fizemos uso do findByType para chamar a função onPress, que irá disparar a navegação

Aqui novamente pegamos um erro na implementação, quando a prop item não é passada. Para corrigir atualize a defaultProps no src/components/TeamItem.js:

TeamItem.defaultProps = {
  item: {},
  navigation: undefined,
};

Já os testes do TeamsList nos traz uma complicação para testar devido a nossa implementação, pois nela temos as seguintes inconveniências:

  • Nosso componente é “smart” demais, com isso precisamos utilizar o nock, para simular o retorno da API. Em questão de teste, no fim até é bom, pois estamos fazendo um teste mais completo, de integração (mas iremos manter o teste na pasta unit, por questões de praticidade para o artigo)
  • Agora o grande problema é com o nosso TeamItem, que utiliza o HOC withNavigation, que não está disponível no ambiente de teste. Devido a isso teremos que testar utilizando o pacote de shallow do react-test-renderer (aproveito para recomendar o uso do enzyme, que é uma lib bastante utilizada para testes de componentes React, por oferecer mais recursos aos testes)
  • Como estamos simulando com o nock, a execução do componentDidMount ainda será assíncrona, portanto, para esperar criamos uma função para esperar a execução dos métodos assíncronos no Event Loop, o asyncFlush

Se você está utilizando o código do artigo anterior, atualize onde usamos o componentWillMount, para o componentDidMount, pois o componentWillMount está deprecated no React 16.3 e será removido no 17.

Feita as explicações, os testes ficarão da seguinte forma:

 

Com os últimos testes criados, já garantimos a cobertura de parte do src/api/TeamsApi.js, esse é um benefício de realizar testes de integração, ainda mais em um cenário que o que importa é justamente a integração. Uma vez que nosso componente possui uma lógica mínima.

Para encerrar, vamos testar nossos componentes que estão na pasta screens.

Crie um novo arquivo test/unit/screens/Home.test.js:

O teste acima é bem simples, praticamente um teste “de contrato”, garantindo que o componente TeamsList está sendo carregado, e usando o shallow, justamente por que essa é a única verificação que queremos fazer, não precisamos renderizar o TeamsList , pois acabamos de fazer o teste dele.

Agora o último arquivo a ser criado do dia/noite test/unit/screens/TeamDetails.test.js:

Nosso último teste é parecido com o teste do TeamsList, onde precisamos tratar os cenários antes e após os dados serem carregados.

Finalizando

Uma última alteração no projeto, vamos criar um script, para rodar os testes e gerar o relatório de cobertura. Basta adicionar a seguinte linha logo após o script test: "test:coverage": "jest --coverage '--collectCoverageFrom=src/**/*.js' '--collectCoverageFrom=src/**/*.jsx'",

Agora rode yarn run test:coverage, e você verá o resultado dos testes com o relatório de cobertura (também é gerada a versão HTML em coverage/lcov-report/index.html):

Alcançamos uma excelente cobertura, apenas não conseguindo 100%, pois utilizamos o shallow para testar o TeamsList. E mais importante que a cobertura, foi que conseguimos identificar erros na implementação, melhorá-la e criar testes que nos ajudam a garantir o correto funcionamento da app.

Com isso finalizamos os testes da nossa aplicação. Espero que esse e o artigo anterior, tenham te ajudado a entender como testar aplicações React/React Native, e também conceitos que valem para outras stacks.

Até o próximo artigo!

O código apresentado está disponível no Github