Android

19 out, 2016

Testes no Android com Espresso - Parte 03

Publicidade

No artigo anterior, fizemos nosso primeiro teste na tela de login. Caso queira iniciar a partir desta parte, utilize o branch da part 2 do projeto.

Simulando resultados de intents

Vamos escrever o teste para o seguinte cenário: quando username e password não são vazios e o usuário clica no botão de login, o app deve abrir a tela principal (MainActivity).

Poderíamos escrever um teste que preencha os dois campos. Para isso, clique no botão de login e, em seguida, verifique se o layout da MainActivity estará visível. Algo deste tipo:

@Test
public void whenBothFieldsAreFilled_andClickOnLoginButton_shouldOpenMainActivity() {
  onView(withId(R.id.login_username)).perform(typeText("defaultText"), closeSoftKeyboard());
  onView(withId(R.id.login_password)).perform(typeText("defaultText"), closeSoftKeyboard());
  onView(withId(R.id.login_button)).perform(click());
  onView(withId(R.id.main_activity_container)).check(matches(isDisplayed()));
}

Porém, isso é errado por dois motivos. O primeiro pode ser considerado mais uma dica: evite fazer testes que façam navegações através do app, inicie as activities diretamente no estado desejado.

O segundo motivo é que a MainActivity faz uma requisição para a API, o que torna este teste dependente de um recurso externo. E aí vai outra dica, que na verdade, deve ser uma regra: os testes devem ser isolados de qualquer dependência externa.

Para testarmos se uma activity é iniciada, devemos testar se a intent desta activity é lançada, afinal de contas, quem define qual activity será iniciada é a intent. Para podermos validar a intent, devemos adicionar a seguinte dependência no nosso arquivo build.gradle:

androidTestCompile "com.android.support.test.espresso:espresso-intents:$espressoVersion"

Essa é uma extensão do Espresso que nos permite trabalhar com intents (mais informações neste link).

Antes de escrevermos o teste, vamos analisar o trecho do código que inicia a nossa MainActivity (LoginActivity.java, linha 53).

private void doLogin() {
  startActivity(new Intent(this, MainActivity.class));
}

A intent que estamos montando é bem simples, pois possui apenas o nome da classe (MainActivity.class) e o contexto como parâmetros. Vamos utilizar o nome da classe para validar nossa intent. O nosso teste ficará assim:

@Test
public void whenBothFieldsAreFilled_andClickOnLoginButton_shouldOpenMainActivity() {
  Intents.init();

  onView(withId(R.id.login_username)).perform(typeText("username"), closeSoftKeyboard());
  onView(withId(R.id.login_password)).perform(typeText("password"), closeSoftKeyboard());
  
  Matcher<Intent> matcher = hasComponent(MainActivity.class.getName());
  
  onView(withId(R.id.login_button)).perform(click());
  
  intended(matcher);

  Intents.release();
}

Vamos analisar o código:

  • Linha 3: Estamos iniciando a gravação das intents com o método init();
  • Linhas 5 e 6: Nada de novo, apenas preenchendo os campos username epassword;
  • Linha 8: Usamos o método IntentMatchers.hasComponent(String className) passando como parâmetro o nome da classe MainActivity, que é a activity que será iniciada;
  • Linha 10: Fazemos o click no botão de login;
  • Linha 12: O método intended(Matcher<Intent> matcher) verifica que o matcher passado como parâmetro é o que a activity em teste irá lançar, garantindo também que esta intent seja única;
  • Linha 14: O método release() limpa os estados das intents.

O que estamos fazendo é falar para o Espresso: “Cara, quando eu preencher estes dois campos e clicar no botão de login, o app deve lançar uma intent para abrir a MainActivity”. Simples assim.

Se rodarmos o teste, ele passa. Porém, ainda há algo errado nele. Repare que a MainActivity ainda abre. E faz sentido, afinal de contas, a única coisa que fizemos foi garantir que a intent lançada é a correta. Não fizemos nada para impedir que a MainActivity se inicie.

Para garantir que o nosso teste esteja isolado e evitar a navegação pelo app, vamos simular o resultado desta intent. Refatorando o teste, vai ficar assim:

@Test
public void whenBothFieldsAreFilled_andClickOnLoginButton_shouldOpenMainActivity() {
  Intents.init();
  onView(withId(R.id.login_username)).perform(typeText("defaultText"), closeSoftKeyboard());
  onView(withId(R.id.login_password)).perform(typeText("defaultText"), closeSoftKeyboard());
  Matcher<Intent> matcher = hasComponent(MainActivity.class.getName());

  ActivityResult result = new ActivityResult(Activity.RESULT_OK, null);
  intending(matcher).respondWith(result);

  onView(withId(R.id.login_button)).perform(click());
  intended(matcher);
  Intents.release();
}

Foram adicionadas duas linhas:

  • Linha 8: Estamos criando um objeto ActivityResult que irá simular o resultado da activity;
  • Linha 9: Usamos o método intending() para devolver um resultado assim que a intent for lançada.

Um ponto importante na documentação do Espresso sobre esse método:

Note: the destination activity will not be launched.

  • Linha 9: Chamamos o método respondWith(), passando como parâmetro o nosso objeto ActivityResult definido na linha 8.

Novamente, o que estamos falando para o Espresso é: “Quando esta intent for lançada, responda com esse resultado”, evitando que a MainActivity inicie.

Rode o teste, ele deve passar sem problemas e a MainActivity não será iniciada. Se algo deu errado, retome os passos anteriores ou deixe um comentário abaixo para eu poder te ajudar.

Ótimo, agora que já cobrimos nossa primeira tela com testes, podemos seguir em frente. Antes, verifique se o seu código está parecido com o que está na branch ‘part_3’ do repositório.

Se tiver alguma dúvida, sugestão, ou se encontrou um erro no artigo, deixe um comentário.