React Native e Detox
Criando testes E2E para simular seu usário!
React Native tem um ecossistema incrível, existem vários recursos para acessar e todo dia, um artigo novo pipoca no Medium.
No meio de tudo isso, algumas bibliotecas se destacam. Hoje vamos ver uma biblioteca criada pela galera do Wix.com, focada especialmente para testes funcionais/E2E, simulando o uso do seu app como um usuário final, é a famosa Detox.
Detox é uma biblioteca que cria um ambiente de teste seguro e isolado (gray box tests), possibilitando que você crie testes funcionais de ponta a ponta (functional tests / e2e tests).
Como os testes são escritos
Se você já escreveu testes em Mocha ou Jest, eles são bem parecidos:
describe('Tela Inicial', () => {
it('deve mostrar o texto correto', async () => {
await device.reloadReactNative();
await expect(element(by.text('Olá, seja bem-vindo!'))).toBeVisible();
});
});
Incrível não? O mais interessante, é que por trás desse teste, temos algumas etapas como:
- Detox irá rodar os testes baseado na versão compilada do seu projeto
- Detox irá instalar essa versão em um simulador e executar os testes isoladamente
- Detox tem uma extensa documentação sobre ações, ciclos de vida e como selecionar elementos
- Detox disponibiliza várias opções para debbugar seu projeto
Facilitando e muito a nossa vida ao escrever testes não-unitários.
Instalando as depêndencias
Nesse exemplo, iremos rodar um projeto no simulador do iOS. Iremos falar sobre Android em um artigo futuro. Para usar Detox com iOS, os requesitos básicos são:
1. Sistema Operacional:
- Mac com macOS, no mínimo, El Captain 10.11
- Xcode 8.3 com a linha de comando do Xcode instalada (para verificar se você já tem ela instalada, você pode rodar
gcc -v
, um alerta do sistema irá aparecer caso você não tenha).
2. Instale a versão mais recente do Homebrew
Acessando https://brew.sh/
3. Tenha o Node.js 7.6.0+ instalado
Você pode utilizar Homebrew para isso:
brew update && brew install node
Verifique se está corretamente instalado rodando node -v
(lembre-se, acima de 7.6.0+).
4. Instale appleSimUtils
Uma coleção de ferramentas para os simuladores da Apple. Detox usa essa interface para se comunicar com eles:
brew tap wix/brew
brew install --HEAD applesimutils
Verifique se está corretamente instalado rodando applesimutils
no seu terminal.
Iniciando um projeto
Assim como no artigo anterior. Não irei falar sobre os requesitos básicos para instalar React Native na sua máquina. Para isso, eu recomendo você ir na documentação oficial e seguir o Getting Started.
Vamos iniciar um novo projeto:
react-native init ExemploDetox
Vamos instalar o pacote do Detox e, por padrão, a equipe do Detox recomenda o uso do Mocha para rodar os testes:
cd ExemploDetox
npm install --save-dev detox mocha
Vamos adicionar a configuração básica ao nosso package.json:
...
"detox": {
"configurations": {
"ios.sim.debug": {
"binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/NOME_DO_SEU_APP.app",
"build": "xcodebuild -project ios/NOME_DO_SEU_APP.xcodeproj -scheme NOME_DO_SEU_APP -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
"type": "ios.simulator",
"name": "iPhone 7"
}
}
}
...
Perceba a chave NOME_DO_SEU_APP
acima, e substitua ela pelo nome do seu projeto, no nosso exemplo, iremos alterar para: ExemploDetox.app
, ExemploDetox.xcodeproj
e ExemploDetox
respectivamente.
Outro ponto importante, é saber se o simulador selecionado (nesse caso iPhone 7), está instalado na sua máquina. Para verificar quais simuladores você tem, você pode rodar xcrun simctl list
no seu terminal para listar os simuladores disponíveis.
Agora, iremos pedir para o Detox criar seus arquivos iniciais com:
./node_modules/.bin/detox init
Aqui você tem outras opções como:
- Pode instalar o
detox-cli
globalmente comnpm install -g detox-cli
e rodardetox init
- Utilizar
yarn
para rodar os binários instalados no seunode_modules
comyarn run detox init
- Adicionar um
npm script
para fazer o link do binário com sua linha de comando e utilizarnpm run detox
(para passar parâmetros, utilize--
)
...
"scripts": {
...
"detox": "detox"
}
...
Para facilitar nossa uso, vamos adicionar alguns scripts:
...
"scripts": {
...
"detox:build": "detox build",
"detox:test": "detox test"
}
...
Estrutura inicial
Antes de rodar nosso comando, vamos tirar alguns minutos para conhecer a estrutura inicial gerada pelo Detox. Já instalamos as depêndencias de sistema, de projeto e iniciamos uma estrutura básica. Teremos uma pasta e2e
localizada na raíz do nosso projeto:
.babelrc
.buckconfig
.flowconfig
.gitattributes
.gitignore
.watchmanconfig
App.js
__tests__
android
app.json
e2e // <= pasta criada pelo "detox init"
index.js
ios
node_modules
package-lock.json
package.json
yarn.lock
Os arquivos iniciais são:
firstTest.spec.js
init.js
mocha.opts
Vamos abrir o firstTest.spec.js
:
describe('Example', () => {
beforeEach(async () => {
await device.reloadReactNative();
});
it('should have welcome screen', async () => {
await expect(element(by.id('welcome'))).toBeVisible();
});
it('should show hello screen after tap', async () => {
await element(by.id('hello_button')).tap();
await expect(element(by.text('Hello!!!'))).toBeVisible();
});
it('should show world screen after tap', async () => {
await element(by.id('world_button')).tap();
await expect(element(by.text('World!!!'))).toBeVisible();
});
})
Para entender melhor o que está acontecendo, vamos analisar essa suíte de testes.
Analisando exemplo inicial
- Temos um gancho
beforeEach
que diz ao Detox para recarregar o seu aplicativo React Native antes de cada teste na sua suíte.
beforeEach(async () => {
await device.reloadReactNative();
});
- Nosso primeiro teste que contém uma assertação que busca por um elemento pelo seu id,
element(by.id(...))
e espera que ele esteja visível,expect(….).toBeVisible()
:
it('should have welcome screen', async () => {
await expect(element(by.id('welcome'))).toBeVisible();
});
Para fazer esse teste passar, precisamos adicionar um testID com valor “welcome” na primeira tela do nosso projeto. Vamos abrir App.js
e adicionar uma propriedade testID="welcome"
:
<View testID='welcome' style={styles.container}>
<Text style={styles.welcome}>Welcome to React Native!</Text>
<Text style={styles.instructions}>To get started, edit index.ios.js</Text>
<Text style={styles.instructions}>Press Cmd+R to reload,{'\n'}Cmd+D or shake for dev menu</Text>
</View>
- Nosso segundo teste é:
it("should show hello screen after tap", async () => {
await element(by.id("hello_button")).tap();
await expect(element(by.text("Hello!!!"))).toBeVisible();
});
- Primeiro busca um elemento com
testID
de valor “hello_button”, executa um método.tap()
- Em seguida, ele busca um elemento que contenha um texto na tela atual pelo valor “Hello!!!”,
element(by.text(...))
. - Esse elemento deve estar visível, executando
.toBeVisible();
O terceiro teste é semelhante ao descrito acima. Nós não iremos adicionar o estado/botão necessário para fazer esses testes passarem. Fica o exercício para o leitor ;)
Detox oferece uma gama de métodos para ações em um elemento, encontrar um elemento na tela atual e assertações.
Você pode conferir a documentação oficial, que é bem completa!
Executando nossos testes
Se você alterou os nomes corretamente como mostrado anteriorment (NOME_DO_SEU_APP
), vamos executar:
npm run detox:build && \ [A]
npm run detox:test [B]
- [A] o comando executado,
detox build
, irá executar o Metro Bundler (caso não haja nenhum atualmente) e disponibilizar seu projeto - [B] irá executar o seu aplicativo isoladamente, com logs no seu terminal
As seguintes etapas devem ocorrer:
- o comando
debox build
deve executar o Metro Bundler e compilar seu projeto
- o comando
debox test
deve consumir seu projeto e começar a execução dos testes
- como são testes de exemplo, eles devem falhar
Fazendo nossos testes passarem
Se você chegou até aqui, viu que adicionamos um testID
com valor “welcome” na nossa tela principal. Com isso, vamos deletar os outros dois testes exemplo e atualizar firstTest.spec.js
para:
describe("Example", () => {
beforeEach(async () => {
await device.reloadReactNative();
}); it("should have welcome screen", async () => {
await expect(element(by.id("welcome"))).toBeVisible();
});
});
E ao rodar npm run detox:test
novamente, teremos o seguinte resultado:
Yaaahoo! Concluimos a integração inicial de Detox a seu projeto React Native! 🎉🎉🎉
Finalizando
Como mencionei acima, a documentação do Detox é incrível e com exemplos de fluxo de trabalho para seu dia-a-dia.
E como disse Guillermo Rauch:
Detox é a ferramenta que faltava para completar seu ciclo de testes.
Vale a pena seguir o Tal Kol e Rotem Mizrachi-Meidan para ficar por dentro das últimas novidades da equipe de engenharia do Wix.
Quer contribuir? Acesse o repositório oficial ou dê um ping no Twitter Wix Engineering.