Cadre de test Espresso - Intentions

Android Intent est utilisé pour ouvrir une nouvelle activité, soit interne (ouverture d'un écran de détail de produit à partir de l'écran de liste de produits) soit externe (comme l'ouverture d'un composeur pour passer un appel). L'activité d'intention interne est gérée de manière transparente par le cadre de test espresso et ne nécessite aucun travail spécifique de la part de l'utilisateur. Cependant, invoquer une activité externe est vraiment un défi car cela sort de notre champ d'application, l'application testée. Une fois que l'utilisateur appelle une application externe et sort de l'application testée, les chances que l'utilisateur revienne à l'application avec une séquence d'actions prédéfinie sont plutôt moindres. Par conséquent, nous devons assumer l'action de l'utilisateur avant de tester l'application. Espresso propose deux options pour gérer cette situation. Ils sont comme suit,

prévu

Cela permet à l'utilisateur de s'assurer que l'intention correcte est ouverte à partir de l'application testée.

l'intention

Cela permet à l'utilisateur de se moquer d'une activité externe comme prendre une photo de l'appareil photo, composer un numéro à partir de la liste de contacts, etc., et revenir à l'application avec un ensemble de valeurs prédéfinies (comme l'image prédéfinie de l'appareil photo au lieu de l'image réelle) .

Installer

Espresso prend en charge l'option d'intention via une bibliothèque de plugins et la bibliothèque doit être configurée dans le fichier gradle de l'application. L'option de configuration est la suivante,

dependencies {
   // ...
   androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1'
}

prévu()

Le plug-in d'intention Espresso fournit des correspondances spéciales pour vérifier si l'intention invoquée est l'intention attendue. Les matchers fournis et le but des matchers sont les suivants,

hasAction

Cela accepte l'action d'intention et renvoie un matcher, qui correspond à l'intention spécifiée.

hasData

Cela accepte les données et renvoie un matcher, qui fait correspondre les données fournies à l'intention lors de son appel.

toPackage

Cela accepte le nom du package d'intention et renvoie un matcher, qui correspond au nom du package de l'intention invoquée.

Maintenant, créons une nouvelle application et testons l'application pour une activité externe en utilisant intention () pour comprendre le concept.

  • Démarrez le studio Android.

  • Créez un nouveau projet comme indiqué précédemment et nommez-le IntentSampleApp.

  • Migrez l'application vers le framework AndroidX à l'aide de Refactor → Migrer vers le menu d'options AndroidX .

  • Créez une zone de texte, un bouton pour ouvrir la liste de contacts et un autre pour passer un appel en modifiant le activity_main.xml comme indiqué ci-dessous,

<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
   xmlns:app = "http://schemas.android.com/apk/res-auto"
   xmlns:tools = "http://schemas.android.com/tools"
   android:layout_width = "match_parent"
   android:layout_height = "match_parent"
   tools:context = ".MainActivity">
   <EditText
      android:id = "@+id/edit_text_phone_number"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content"
      android:layout_centerHorizontal = "true"
      android:text = ""
      android:autofillHints = "@string/phone_number"/>
   <Button
      android:id = "@+id/call_contact_button"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content"
      android:layout_centerHorizontal = "true"
      android:layout_below = "@id/edit_text_phone_number"
      android:text = "@string/call_contact"/>
   <Button
      android:id = "@+id/button"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content"
      android:layout_centerHorizontal = "true"
      android:layout_below = "@id/call_contact_button"
      android:text = "@string/call"/>
</RelativeLayout>
  • Ajoutez également l'élément ci-dessous dans le fichier de ressources strings.xml ,

<string name = "phone_number">Phone number</string>
<string name = "call">Call</string>
<string name = "call_contact">Select from contact list</string>
  • Maintenant, ajoutez le code ci-dessous dans l'activité principale ( MainActivity.java ) sous la méthode onCreate .

public class MainActivity extends AppCompatActivity {
   @Override
   protected void onCreate(Bundle savedInstanceState) {
      // ... code
      // Find call from contact button
      Button contactButton = (Button) findViewById(R.id.call_contact_button);
      contactButton.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View view) {
            // Uri uri = Uri.parse("content://contacts");
            Intent contactIntent = new Intent(Intent.ACTION_PICK,
               ContactsContract.Contacts.CONTENT_URI);
            contactIntent.setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE);
            startActivityForResult(contactIntent, REQUEST_CODE);
         }
      });
      // Find edit view
      final EditText phoneNumberEditView = (EditText)
         findViewById(R.id.edit_text_phone_number);
      // Find call button
      Button button = (Button) findViewById(R.id.button);
      button.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View view) {
            if(phoneNumberEditView.getText() != null) {
               Uri number = Uri.parse("tel:" + phoneNumberEditView.getText());
               Intent callIntent = new Intent(Intent.ACTION_DIAL, number);
               startActivity(callIntent);
            }
         }
      });
   }
   // ... code
}

Ici, nous avons programmé le bouton avec id, call_contact_button pour ouvrir la liste de contacts et bouton avec id, bouton pour composer l'appel.

  • Ajoutez une variable statique REQUEST_CODE dans la classe MainActivity comme indiqué ci-dessous,

public class MainActivity extends AppCompatActivity {
   // ...
   private static final int REQUEST_CODE = 1;
   // ...
}
  • Maintenant, ajoutez la méthode onActivityResult dans la classe MainActivity comme ci-dessous,

public class MainActivity extends AppCompatActivity {
   // ...
   @Override
   protected void onActivityResult(int requestCode, int resultCode, Intent data) {
      if (requestCode == REQUEST_CODE) {
         if (resultCode == RESULT_OK) {
            // Bundle extras = data.getExtras();
            // String phoneNumber = extras.get("data").toString();
            Uri uri = data.getData();
            Log.e("ACT_RES", uri.toString());
            String[] projection = {
               ContactsContract.CommonDataKinds.Phone.NUMBER, 
               ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME };
            Cursor cursor = getContentResolver().query(uri, projection, null, null, null);
            cursor.moveToFirst();
            
            int numberColumnIndex =
               cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
            String number = cursor.getString(numberColumnIndex);
            
            int nameColumnIndex = cursor.getColumnIndex(
               ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
            String name = cursor.getString(nameColumnIndex);
            Log.d("MAIN_ACTIVITY", "Selected number : " + number +" , name : "+name);
            
            // Find edit view
            final EditText phoneNumberEditView = (EditText)
               findViewById(R.id.edit_text_phone_number);
            phoneNumberEditView.setText(number);
         }
      }
   };
   // ...
}

Ici, onActivityResult sera invoqué lorsqu'un utilisateur revient dans l'application après avoir ouvert la liste de contacts à l'aide du bouton call_contact_button et sélectionné un contact. Une fois que la méthode onActivityResult est appelée, elle obtient le contact sélectionné par l'utilisateur, trouve le numéro de contact et le définit dans la zone de texte.

  • Exécutez l'application et assurez-vous que tout va bien. L'aspect final de l' exemple d'application Intent est illustré ci-dessous,

  • Maintenant, configurez l'intention espresso dans le fichier gradle de l'application comme indiqué ci-dessous,

dependencies {
   // ...
   androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1'
}
  • Cliquez sur l' option de menu Synchroniser maintenant fournie par Android Studio. Cela téléchargera la bibliothèque de test d'intention et la configurera correctement.

  • Ouvrez ExampleInstrumentedTest.java fichier et ajoutez le IntentsTestRule au lieu de normalement utilisé AndroidTestRule . IntentTestRule est une règle spéciale pour gérer les tests d'intention.

public class ExampleInstrumentedTest {
   // ... code
   @Rule
   public IntentsTestRule<MainActivity> mActivityRule =
   new IntentsTestRule<>(MainActivity.class);
   // ... code
}
  • Ajoutez deux variables locales pour définir le numéro de téléphone de test et le nom du package du numéroteur comme ci-dessous,

public class ExampleInstrumentedTest {
   // ... code
   private static final String PHONE_NUMBER = "1 234-567-890";
   private static final String DIALER_PACKAGE_NAME = "com.google.android.dialer";
   // ... code
}
  • Résolvez les problèmes d'importation en utilisant l'option Alt + Entrée fournie par Android Studio ou incluez les instructions d'importation ci-dessous,

import android.content.Context;
import android.content.Intent;

import androidx.test.InstrumentationRegistry;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.intent.Intents.intended;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasData;
import static androidx.test.espresso.intent.matcher.IntentMatchers.toPackage;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.core.AllOf.allOf;
import static org.junit.Assert.*;
  • Ajoutez le cas de test ci-dessous pour tester si le numéroteur est correctement appelé,

public class ExampleInstrumentedTest {
   // ... code
   @Test
   public void validateIntentTest() {
      onView(withId(R.id.edit_text_phone_number))
         .perform(typeText(PHONE_NUMBER), closeSoftKeyboard());
      onView(withId(R.id.button)) .perform(click());
      intended(allOf(
         hasAction(Intent.ACTION_DIAL),
         hasData("tel:" + PHONE_NUMBER),
         toPackage(DIALER_PACKAGE_NAME)));
   }
   // ... code
}

Ici, les matchers hasAction , hasData et toPackage sont utilisés avec allOf matcher pour réussir uniquement si tous les matchers sont passés.

  • Maintenant, exécutez le ExampleInstrumentedTest via le menu de contenu dans le studio Android.

intention ()

Espresso fournit une méthode spéciale - l' intention () de simuler une action d'intention externe. intending () accepte le nom du package de l'intention à se moquer et fournit une méthode respondWith pour définir la manière dont l'intention fictive doit être traitée comme spécifié ci-dessous,

intending(toPackage("com.android.contacts")).respondWith(result);

Ici, respondWith () accepte le résultat d'intention de type Instrumentation.ActivityResult . Nous pouvons créer une nouvelle intention de stub et définir manuellement le résultat comme indiqué ci-dessous,

// Stub intent
Intent intent = new Intent();
intent.setData(Uri.parse("content://com.android.contacts/data/1"));
Instrumentation.ActivityResult result =
   new Instrumentation.ActivityResult(Activity.RESULT_OK, intent);

Le code complet pour tester si une application de contact est correctement ouverte est le suivant,

@Test
public void stubIntentTest() {
   // Stub intent
   Intent intent = new Intent();
   intent.setData(Uri.parse("content://com.android.contacts/data/1"));
   Instrumentation.ActivityResult result =
      new Instrumentation.ActivityResult(Activity.RESULT_OK, intent);
   intending(toPackage("com.android.contacts")).respondWith(result);
   
   // find the button and perform click action
   onView(withId(R.id.call_contact_button)).perform(click());
   
   // get context
   Context targetContext2 = InstrumentationRegistry.getInstrumentation().getTargetContext();
   
   // get phone number
   String[] projection = { ContactsContract.CommonDataKinds.Phone.NUMBER,
      ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME };
   Cursor cursor =
      targetContext2.getContentResolver().query(Uri.parse("content://com.android.cont
      acts/data/1"), projection, null, null, null);
   
   cursor.moveToFirst();
   int numberColumnIndex =
      cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
   String number = cursor.getString(numberColumnIndex);
   
   // now, check the data
   onView(withId(R.id.edit_text_phone_number))
   .check(matches(withText(number)));
}

Ici, nous avons créé un nouvel intent et défini la valeur de retour (lors de l'appel de l'intention) comme première entrée de la liste de contacts, content: //com.android.contacts/data/1 . Ensuite, nous avons défini la méthode d' intention pour simuler l'intention nouvellement créée à la place de la liste de contacts. Il définit et appelle notre intention nouvellement créée lorsque le package, com.android.contacts est appelé et que la première entrée par défaut de la liste est renvoyée. Ensuite, nous avons déclenché l' action click () pour démarrer l'intention fictive et enfin vérifier si le numéro de téléphone de l'appel de l'intention fictive et le numéro de la première entrée dans la liste de contacts sont identiques.

S'il y a un problème d'importation manquant, corrigez ces problèmes d'importation en utilisant l'option Alt + Entrée fournie par android studio ou incluez les instructions d'importation ci-dessous,

import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;

import androidx.test.InstrumentationRegistry;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.intent.Intents.intended;
import static androidx.test.espresso.intent.Intents.intending;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasData;
import static androidx.test.espresso.intent.matcher.IntentMatchers.toPackage;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.core.AllOf.allOf;
import static org.junit.Assert.*;

Ajoutez la règle ci-dessous dans la classe de test pour fournir l'autorisation de lire la liste de contacts -

@Rule
public GrantPermissionRule permissionRule =
GrantPermissionRule.grant(Manifest.permission.READ_CONTACTS);

Ajoutez l'option ci-dessous dans le fichier manifeste de l'application, AndroidManifest.xml -

<uses-permission android:name = "android.permission.READ_CONTACTS" />

Maintenant, assurez-vous que la liste de contacts contient au moins une entrée, puis exécutez le test à l'aide du menu contextuel d'Android Studio.