Teste de unidade em aplicativos nativos de reação
Publicados: 2022-03-10O React Native é um dos frameworks mais usados para construir aplicativos móveis. Este tutorial é destinado a desenvolvedores que desejam começar a testar aplicativos React Native que eles criam. Faremos uso do framework de testes Jest e Enzyme.
Neste artigo, aprenderemos os princípios básicos de teste, exploraremos várias bibliotecas para testar um aplicativo e veremos como testar unidades (ou componentes) de um aplicativo React Native. Ao trabalhar com um aplicativo React Native, solidificamos nosso conhecimento de testes.
Nota: O conhecimento básico de JavaScript e React Native seria de grande benefício enquanto você trabalha neste tutorial.
O que é teste unitário?
O teste de unidade é o nível de teste no qual os componentes individuais do software são testados. Fazemos isso para garantir que cada componente funcione conforme o esperado. Um componente é a menor parte testável do software.
Para ilustrar, vamos criar um componente Button
e simular um teste unitário:
import React from 'react'; import { StyleSheet, Text, TouchableOpacity } from 'react-native'; function AppButton({ onPress }) { return ( <TouchableOpacity style={[styles.button, { backgroundColor: colors[color] }]} onPress={onPress} > <Text style={styles.text}>Register</Text> </TouchableOpacity> ); } const styles = StyleSheet.create({ button: { backgroundColor: red; borderRadius: 25, justifyContent: 'center', alignItems: 'center', }, text: { color: #fff } }) export default AppButton;
Este componente Button
tem texto e uma função onPress
. Vamos testar este componente para ver do que se trata o teste de unidade.
Primeiro, vamos criar um arquivo de teste, chamado Button.test.js
:
it('renders correctly across screens', () => { const tree = renderer.create(<Button />).toJSON(); expect(tree).toMatchSnapshot(); });
Aqui, estamos testando para ver se nosso componente Button
é renderizado como deveria em todas as telas do aplicativo. É disso que se trata o teste de unidade: testar componentes de um aplicativo para garantir que funcionem como deveriam.
Teste de unidade em aplicativos nativos de reação
Um aplicativo React Native pode ser testado com uma variedade de ferramentas, algumas das quais são as seguintes:
- WebDriver
Essa ferramenta de teste de código aberto para aplicativos Node.js também é usada para testar aplicativos React Native. - Pesadelo
Isso automatiza as operações de teste no navegador. De acordo com a documentação, “o objetivo é expor alguns métodos simples que imitam as ações do usuário (comogoto
,type
eclick
), com uma API que pareça síncrona para cada bloco de script, em vez de retornos de chamada profundamente aninhados”. - brincadeira
Esta é uma das bibliotecas de teste mais populares e aquela em que nos concentraremos hoje. Assim como o React, ele é mantido pelo Facebook e foi feito para fornecer uma configuração “zero config” para desempenho máximo. - Mocha
Mocha é uma biblioteca popular para testar aplicativos React e React Native. Tornou-se uma ferramenta de teste de escolha para os desenvolvedores devido à facilidade de configuração e uso e à rapidez com que é. - Jasmim
De acordo com sua documentação, Jasmine é uma estrutura de desenvolvimento orientada por comportamento para testar código JavaScript.
Introdução ao Jest e Enzima
De acordo com sua documentação, “Jest é um framework de teste JavaScript delicioso com foco na simplicidade”. Funciona com configuração zero. Após a instalação (usando um gerenciador de pacotes como npm ou Yarn), o Jest está pronto para uso, sem a necessidade de outras instalações.
Enzyme é uma estrutura de teste JavaScript para aplicativos React Native. (Se você estiver trabalhando com React em vez de React Native, um guia está disponível.) Usaremos Enzyme para testar unidades da saída de nosso aplicativo. Com ele, podemos simular o tempo de execução do aplicativo.
Vamos começar configurando nosso projeto. Usaremos o aplicativo Done With It no GitHub. É um mercado de aplicativos React Native. Comece clonando-o, navegue até a pasta e instale os pacotes executando o seguinte para npm…
npm install
… ou isto para Fios:
yarn install
Este comando instalará todos os pacotes em nosso aplicativo. Feito isso, testaremos a consistência da interface do usuário do nosso aplicativo usando instantâneos, abordados abaixo.
Snapshots e configuração do Jest
Nesta seção, testaremos os toques do usuário e a interface do usuário dos componentes do aplicativo testando instantâneos usando Jest.
Antes de fazer isso, precisamos instalar o Jest e suas dependências. Para instalar o Jest para Expo React Native, execute o seguinte comando:
yarn add jest-expo --dev
Isso instala jest-expo
no diretório do nosso aplicativo. Em seguida, precisamos atualizar nosso arquivo package.json
para ter um script de teste:
"scripts": { "test" "jest" }, "jest": { "preset": "jest-expo" }
Ao adicionar este comando, estamos dizendo ao Jest qual pacote registrar em nosso aplicativo e onde.
Em seguida, adicionamos outros pacotes ao nosso aplicativo que ajudarão o Jest a fazer um teste abrangente. Para npm, execute este…
npm i react-test-renderer --save-dev
… e para Yarn, isto:
yarn add react-test-renderer --dev
Ainda temos um pouco de configuração para fazer em nosso arquivo package.json
. De acordo com a documentação do Expo React Native, precisamos adicionar uma configuração transformIgnorePattern
que impeça a execução de testes em Jest sempre que um arquivo de origem corresponder a um teste (ou seja, se um teste for feito e um arquivo semelhante for encontrado nos node modules
do projeto).
"jest": { "preset": "jest-expo", "transformIgnorePatterns": [ "node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|@sentry/.*)" ] }
Agora, vamos criar um novo arquivo, chamado App.test.js
, para escrever nosso primeiro teste. Vamos testar se nosso App
tem um elemento filho em sua árvore:
import React from "react"; import renderer from "react-test-renderer"; import App from "./App.js" describe("<App />", () => { it('has 1 child', () => { const tree = renderer.create(<App />).toJSON(); expect(tree.children.length).toBe(1); }); });
Agora, execute yarn test
ou seu equivalente npm. Se App.js
tiver um único elemento filho, nosso teste deverá passar, o que será confirmado na interface de linha de comando.
No código acima, importamos React
e react-test-renderer
, que renderiza nossos testes para Expo
. Convertemos a árvore de componentes <App />
em JSON e, em seguida, pedimos ao Jest para ver se o número retornado de componentes filhos em JSON é igual ao que esperamos.
Mais testes de instantâneos
Como afirma David Adeneye:
“Um teste de instantâneo garante que a interface do usuário (UI) de um aplicativo da Web não seja alterada inesperadamente. Ele captura o código de um componente em um momento, para que possamos comparar o componente em um estado com qualquer outro estado possível.”
Isso é feito especialmente quando um projeto envolve estilos globais que são usados em muitos componentes. Vamos escrever um teste de instantâneo para App.js
para testar a consistência da interface do usuário:
it('renders correctly across screens', () => { const tree = renderer.create( ).toJSON(); expect(tree).toMatchSnapshot(); });
it('renders correctly across screens', () => { const tree = renderer.create( ).toJSON(); expect(tree).toMatchSnapshot(); });
Adicione isso aos testes que já escrevemos e execute yarn test
(ou seu equivalente npm). Se nosso teste passar, devemos ver isso:
PASS src/App.test.js √ has 1 child (16ms) √ renders correctly (16ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 1 total Time: 24s
Isso nos diz que nossos testes foram aprovados e o tempo que eles levaram. Seu resultado será semelhante se os testes forem aprovados.
Vamos passar a zombar de algumas funções no Jest.
Simulando chamadas de API
De acordo com a documentação de Jest:
As funções simuladas permitem que você teste os links entre o código apagando a implementação real de uma função, capturando chamadas para a função (e os parâmetros passados nessas chamadas), capturando instâncias de funções construtoras quando instanciadas com `new` e permitindo testes. configuração de tempo dos valores de retorno.
Simplificando, um mock é uma cópia de um objeto ou função sem o funcionamento real dessa função. Imita essa função.
Os mocks nos ajudam a testar aplicativos de várias maneiras, mas o principal benefício é que eles reduzem nossa necessidade de dependências.
As simulações geralmente podem ser realizadas de duas maneiras. Uma é criar uma função simulada que é injetada no código a ser testado. A outra é escrever uma função simulada que substitua o pacote ou dependência anexado ao componente.
A maioria das organizações e desenvolvedores preferem escrever simulações manuais que imitam a funcionalidade e usam dados falsos para testar alguns componentes.
React Native inclui fetch
no objeto global. Para evitar fazer chamadas de API reais em nosso teste de unidade, zombamos delas. Abaixo está uma maneira de zombar de todas, se não da maioria, de nossas chamadas de API em React Native, e sem a necessidade de dependências:
global.fetch = jest.fn(); // mocking an API success response once fetch.mockResponseIsSuccess = (body) => { fetch.mockImplementationForOnce ( () => Promise.resolve({json: () => Promise.resolve(JSON.parse(body))}) ); }; // mocking an API failure response for once fetch.mockResponseIsFailure = (error) => { fetch.mockImplementationForOnce( () => Promise.reject(error) ); };
Aqui, escrevemos uma função que tenta buscar uma API uma vez. Feito isso, ele retorna uma promessa e, quando resolvido, retorna o corpo em JSON. É semelhante à resposta simulada para uma transação de busca com falha — ela retorna um erro.
Abaixo está o componente product
da nossa aplicação, contendo um objeto product
e retornando as informações como props
.
import React from 'react'; const Product = () => { const product = { name: 'Pizza', quantity: 5, price: '$50' } return ( <> <h1>Name: {product.name}</h1> <h1>Quantity: {product.quantity}</h1> <h1>Price: {product.price}</h1> </> ); } export default Product;
Vamos imaginar que estamos tentando testar todos os componentes do nosso produto. O acesso direto ao nosso banco de dados não é uma solução viável. É aqui que os mocks entram em cena. No código abaixo, estamos tentando zombar de um componente do produto usando Jest para descrever os objetos no componente.
describe("", () => { it("accepts products props", () => { const wrapper = mount(<Customer product={product} />); expect(wrapper.props().product).toEqual(product); }); it("contains products quantity", () => { expect(value).toBe(3); }); });
Estamos usando a describe
do Jest para ditar os testes que queremos que sejam feitos. No primeiro teste, estamos verificando se o objeto que estamos passando é igual aos adereços que zombamos.
No segundo teste, estamos passando os adereços do customer
para garantir que seja um produto e que corresponda aos nossos mocks. Ao fazer isso, não precisamos testar todos os componentes do nosso produto e também evitamos bugs em nosso código.
Zombando de solicitações de API externas
Até agora, estávamos executando testes para chamadas de API com outros elementos em nosso aplicativo. Agora vamos simular uma chamada de API externa. Vamos usar o Axios. Para testar uma chamada externa para uma API, temos que simular nossas solicitações e também gerenciar as respostas que recebemos. Vamos usar axios-mock-adapter
para simular o Axios. Primeiro, precisamos instalar axios-mock-adapter
executando o comando abaixo:
yarn add axios-mock-adapter
A próxima coisa a fazer é criar nossos mocks:
import MockAdapter from 'axios-mock-adapter'; import Faker from 'faker' import ApiClient from '../constants/api-client'; import userDetails from 'jest/mockResponseObjects/user-objects'; let mockApi = new MockAdapter(ApiClient.getAxiosInstance()); let validAuthentication = { name: Faker.internet.email(), password: Faker.internet.password() mockApi.onPost('requests').reply(config) => { if (config.data === validAuthentication) { return [200, userDetails]; } return [400, 'Incorrect username and password']; });
Aqui, estamos chamando o ApiClient
e passando uma instância do Axios para ele para simular as credenciais do usuário. Estamos usando um pacote chamado faker.js para gerar dados de usuário falsos, como endereço de e-mail e senha.
A simulação se comporta como esperamos que a API. Se a solicitação for bem-sucedida, obteremos uma resposta com um código de status de 200 para OK. E obteremos um código de status de 400 para uma solicitação incorreta ao servidor, que seria enviada com JSON com a mensagem “Usuário e senha incorretos”.
Agora que nossa simulação está pronta, vamos escrever um teste para uma solicitação de API externa. Como antes, usaremos instantâneos.
it('successful sign in with correct credentials', async () => { await store.dispatch(authenticateUser('[email protected]', 'password')); expect(getActions()).toMatchSnapshot(); }); it('unsuccessful sign in with wrong credentials', async () => { await store.dispatch(authenticateUser('[email protected]', 'wrong credential')) .catch((error) => { expect(errorObject).toMatchSnapshot(); });
Aqui, estamos testando uma entrada bem-sucedida com as credenciais corretas, usando o async await
de JavaScript nativo para manter nossas entradas. Enquanto isso, a função authenticateUser
do Jest autentica a solicitação e garante que ela corresponda aos nossos instantâneos anteriores. Em seguida, testamos um login malsucedido em caso de credenciais erradas, como endereço de e-mail ou senha, e enviamos um erro como resposta.
Agora, execute yarn test
ou npm test
. Tenho certeza que todos os seus testes passarão.
Vamos ver como testar componentes em uma biblioteca de gerenciamento de estado, Redux.
Testando ações e redutores do Redux usando instantâneos
Não há como negar que o Redux é um dos gerenciadores de estado mais usados para aplicativos React. A maior parte da funcionalidade do Redux envolve um dispatch
, que é uma função do armazenamento do Redux que é usada para acionar uma alteração no estado de um aplicativo. Testar o Redux pode ser complicado porque as actions
do Redux crescem rapidamente em tamanho e complexidade. Com os instantâneos do Jest, isso se torna mais fácil. A maioria dos testes com Redux se resume a duas coisas:
- Para testar
actions
, criamosredux-mock-store
e despachamos as actions. - Para testar os redutores, importamos o
reducer
e passamos um objeto de estado e ação para ele.
Abaixo está um teste do Redux com snapshots. Estaremos testando as ações despachadas autenticando o usuário no SIGN-IN
e vendo como a ação LOGOUT
é tratada pelo redutor de user
.
import mockStore from 'redux-mock-store'; import { LOGOUT } from '../actions/logout'; import User from '../reducers/user'; import { testUser } from 'jest/mock-objects'; describe('Testing the sign in authentication', () => { const store = mockStore(); it('user attempts with correct password and succeeds', async () => { await store.dispatch(authenticateUser('[email protected]', 'password')); expect(store.getActions()).toMatchSnapshot(); }); }); describe('Testing reducers after user LOGS OUT', () => { it('user is returned back to initial app state', () => { expect(user(testUser, { type: LOGOUT })).toMatchSnapshot(); }); });
No primeiro teste, estamos descrevendo a autenticação de login e criando uma loja simulada. Fazemos isso importando primeiro um mockStore
do Redux e, em seguida, importando um método chamado testUser
do Jest para nos ajudar a zombar de um usuário. Em seguida, testamos quando o usuário faz login com sucesso no aplicativo usando um endereço de e-mail e uma senha que correspondam aos do nosso repositório de instantâneos. Portanto, o instantâneo garante que os objetos que o usuário está inserindo correspondam sempre que um teste for executado.
No segundo teste, estamos testando quando o usuário faz logout. Assim que nosso snapshot do redutor confirmar que um usuário fez logout, ele retorna ao estado inicial do aplicativo.
Em seguida, testamos executando yarn test
. Se os testes tiverem passado, devemos ver o seguinte resultado:
PASS src/redux/actions.test.js √ user attempts with correct password and succeeds (23ms) √ user is returned back to initial app state (19ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 2 total Time: 31s
Conclusão
Com o Jest, testar aplicativos React Native nunca foi tão fácil, especialmente com snapshots, que garantem que a interface do usuário permaneça consistente, independentemente dos estilos globais. Além disso, o Jest nos permite zombar de certas chamadas e módulos de API em nosso aplicativo. Podemos levar isso adiante testando componentes de um aplicativo React Native.
Recursos adicionais
- “Um guia prático para testar aplicativos nativos de reação com Jest”, David Adeneye, Smashing Magazine
- Documentação Jest
- “Testing With Jest”, documentação Expo React Native
- “Aprendendo a testar React Native With Jest”, Jason Gaare