Espresso Testing Framework - Intenti

Android Intent viene utilizzato per aprire una nuova attività, interna (apertura di una schermata dei dettagli del prodotto dalla schermata dell'elenco dei prodotti) o esterna (come l'apertura di un dialer per effettuare una chiamata). L'attività di intento interno è gestita in modo trasparente dal framework di test espresso e non richiede alcun lavoro specifico da parte dell'utente. Tuttavia, invocare attività esterne è davvero una sfida perché esula dal nostro ambito, l'applicazione in prova. Una volta che l'utente richiama un'applicazione esterna ed esce dall'applicazione sottoposta a test, le possibilità che l'utente torni all'applicazione con una sequenza di azioni predefinita sono piuttosto inferiori. Pertanto, dobbiamo presupporre l'azione dell'utente prima di testare l'applicazione. Espresso offre due opzioni per gestire questa situazione. Sono i seguenti,

previsto

Ciò consente all'utente di assicurarsi che l'intento corretto sia aperto dall'applicazione sotto test.

intenzione

Ciò consente all'utente di simulare un'attività esterna come scattare una foto dalla fotocamera, comporre un numero dall'elenco dei contatti, ecc. E tornare all'applicazione con un insieme predefinito di valori (come l'immagine predefinita dalla fotocamera invece dell'immagine reale) .

Impostare

Espresso supporta l'opzione intent tramite una libreria di plugin e la libreria deve essere configurata nel file gradle dell'applicazione. L'opzione di configurazione è la seguente,

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

previsto ()

Il plug-in Intent Espresso fornisce abbinamenti speciali per verificare se l'intento invocato è l'intento previsto. I matchers forniti e lo scopo dei matchers sono i seguenti,

hasAction

Questo accetta l'azione intent e restituisce un matcher, che corrisponde all'intento specificato.

hasData

Questo accetta i dati e restituisce un matcher, che corrisponde ai dati forniti all'intento mentre lo invoca.

toPackage

Questo accetta il nome del pacchetto dell'intento e restituisce un matcher, che corrisponde al nome del pacchetto dell'intento invocato.

Ora, creiamo una nuova applicazione e testiamo l'applicazione per attività esterna utilizzando designed () per comprendere il concetto.

  • Avvia Android Studio.

  • Crea un nuovo progetto come discusso in precedenza e chiamalo, IntentSampleApp.

  • Migrare l'applicazione al framework AndroidX utilizzando Refactor → Migrate to AndroidX option menu.

  • Crea una casella di testo, un pulsante per aprire l'elenco dei contatti e un altro per effettuare una chiamata modificando activity_main.xml come mostrato di seguito,

<?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>
  • Inoltre, aggiungi l'elemento seguente nel file di risorse strings.xml ,

<string name = "phone_number">Phone number</string>
<string name = "call">Call</string>
<string name = "call_contact">Select from contact list</string>
  • Ora aggiungi il codice seguente nell'attività principale ( MainActivity.java ) con il metodo 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
}

Qui, abbiamo programmato il pulsante con id, call_contact_button per aprire l'elenco dei contatti e il pulsante con id, pulsante per effettuare la chiamata.

  • Aggiungi una variabile statica REQUEST_CODE nella classe MainActivity come mostrato di seguito,

public class MainActivity extends AppCompatActivity {
   // ...
   private static final int REQUEST_CODE = 1;
   // ...
}
  • Ora aggiungi il metodo onActivityResult nella classe MainActivity come di seguito,

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);
         }
      }
   };
   // ...
}

Qui, onActivityResult verrà richiamato quando un utente torna all'applicazione dopo aver aperto l'elenco dei contatti utilizzando il pulsante call_contact_button e selezionando un contatto. Una volta richiamato il metodo onActivityResult , ottiene il contatto selezionato dall'utente, trova il numero del contatto e lo imposta nella casella di testo.

  • Esegui l'applicazione e assicurati che tutto sia a posto. L'aspetto finale dell'applicazione di esempio Intent è come mostrato di seguito,

  • Ora, configura l'intento espresso nel file gradle dell'applicazione come mostrato di seguito,

dependencies {
   // ...
   androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1'
}
  • Fare clic sull'opzione di menu Sincronizza ora fornita da Android Studio. Questo scaricherà la libreria di intent test e la configurerà correttamente.

  • Apri il file ExampleInstrumentedTest.java e aggiungi IntentsTestRule invece di AndroidTestRule normalmente utilizzato . IntentTestRule è una regola speciale per gestire i test di intenti.

public class ExampleInstrumentedTest {
   // ... code
   @Rule
   public IntentsTestRule<MainActivity> mActivityRule =
   new IntentsTestRule<>(MainActivity.class);
   // ... code
}
  • Aggiungi due variabili locali per impostare il numero di telefono di prova e il nome del pacchetto dialer come di seguito,

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
}
  • Risolvi i problemi di importazione utilizzando l'opzione Alt + Invio fornita da Android Studio oppure includi le seguenti istruzioni di importazione,

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.*;
  • Aggiungi il seguente caso di test per verificare se il dialer è chiamato correttamente,

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
}

Qui, hasAction , hasData e toPackage matcher vengono utilizzati insieme a allOf matcher per avere successo solo se vengono passati tutti i matcher.

  • Ora, esegui ExampleInstrumentedTest tramite il menu dei contenuti in Android Studio.

intending ()

Espresso fornisce un metodo speciale: intending () per deridere un'azione di intento esterno. intending () accetta il nome del pacchetto dell'intento da deridere e fornisce un metodo replyWith per impostare come deve essere risposto all'intento deriso come specificato di seguito,

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

Qui, replyWith () accetta il risultato dell'intento di tipo Instrumentation.ActivityResult . Possiamo creare un nuovo intento stub e impostare manualmente il risultato come specificato di seguito,

// 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);

Il codice completo per verificare se un'applicazione di contatto è aperta correttamente è il seguente,

@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)));
}

Qui abbiamo creato un nuovo intento e impostato il valore di ritorno (quando si richiama l'intento) come prima voce dell'elenco dei contatti, contenuto: //com.android.contacts/data/1 . Quindi abbiamo impostato il metodo di intenzione per deridere l'intento appena creato al posto dell'elenco dei contatti. Imposta e chiama il nostro intento appena creato quando viene richiamato il pacchetto com.android.contacts e viene restituita la prima voce predefinita dell'elenco. Quindi, abbiamo attivato l' azione click () per avviare l'intento fittizio e infine controlliamo se il numero di telefono che richiama l'intento fittizio e il numero della prima voce nell'elenco dei contatti sono uguali.

Se è presente un problema di importazione mancante, quindi risolvi tali problemi di importazione utilizzando l'opzione Alt + Invio fornita da Android Studio oppure includi le seguenti istruzioni di importazione,

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.*;

Aggiungi la regola seguente nella classe di test per fornire l'autorizzazione a leggere l'elenco dei contatti -

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

Aggiungi l'opzione seguente nel file manifest dell'applicazione, AndroidManifest.xml -

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

Ora, assicurati che l'elenco dei contatti abbia almeno una voce, quindi esegui il test utilizzando il menu di scelta rapida di Android Studio.