Marco de prueba de Espresso: intenciones

La intención de Android se usa para abrir una nueva actividad, ya sea interna (abriendo una pantalla de detalles del producto desde la pantalla de lista de productos) o externa (como abrir un marcador para hacer una llamada). La actividad de intención interna es manejada de forma transparente por el marco de prueba de espresso y no necesita ningún trabajo específico por parte del usuario. Sin embargo, invocar la actividad externa es realmente un desafío porque sale de nuestro alcance, la aplicación bajo prueba. Una vez que el usuario invoca una aplicación externa y sale de la aplicación bajo prueba, las posibilidades de que el usuario regrese a la aplicación con una secuencia de acción predefinida son bastante menores. Por lo tanto, debemos asumir la acción del usuario antes de probar la aplicación. Espresso ofrece dos opciones para manejar esta situación. Son los siguientes,

destinado a

Esto permite al usuario asegurarse de que se abra la intención correcta desde la aplicación bajo prueba.

pretendiendo

Esto permite que el usuario se burle de una actividad externa como tomar una foto de la cámara, marcar un número de la lista de contactos, etc., y regresar a la aplicación con un conjunto de valores predefinidos (como una imagen predefinida de la cámara en lugar de una imagen real) .

Preparar

Espresso admite la opción de intención a través de una biblioteca de complementos y la biblioteca debe configurarse en el archivo gradle de la aplicación. La opción de configuración es la siguiente,

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

destinado a()

El complemento de intención de Espresso proporciona comparadores especiales para verificar si la intención invocada es la intención esperada. Los emparejadores proporcionados y el propósito de los emparejadores son los siguientes,

hasAction

Esto acepta la acción de intención y devuelve un comparador, que coincide con la intención especificada.

hasData

Esto acepta los datos y devuelve un comparador, que hace coincidir los datos proporcionados con la intención al invocarla.

empacar

Esto acepta el nombre del paquete de la intención y devuelve un comparador, que coincide con el nombre del paquete de la intención invocada.

Ahora, creemos una nueva aplicación y probemos la aplicación en busca de actividad externa utilizando () para comprender el concepto.

  • Inicie Android Studio.

  • Cree un nuevo proyecto como se discutió anteriormente y asígnele el nombre IntentSampleApp.

  • Migre la aplicación al marco de AndroidX usando Refactor → Migrar al menú de opciones de AndroidX .

  • Cree un cuadro de texto, un botón para abrir la lista de contactos y otro para marcar una llamada cambiando el activity_main.xml como se muestra a continuación,

<?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>
  • Además, agregue el elemento siguiente en el archivo de recursos strings.xml ,

<string name = "phone_number">Phone number</string>
<string name = "call">Call</string>
<string name = "call_contact">Select from contact list</string>
  • Ahora, agregue el siguiente código en la actividad principal ( MainActivity.java ) bajo el método 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
}

Aquí, hemos programado el botón con id, call_contact_button para abrir la lista de contactos y el botón con id, botón para marcar la llamada.

  • Agregue una variable estática REQUEST_CODE en la clase MainActivity como se muestra a continuación,

public class MainActivity extends AppCompatActivity {
   // ...
   private static final int REQUEST_CODE = 1;
   // ...
}
  • Ahora, agregue el método onActivityResult en la clase MainActivity como se muestra a continuación,

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

Aquí, onActivityResult se invocará cuando un usuario regrese a la aplicación después de abrir la lista de contactos usando el botón call_contact_button y seleccionar un contacto. Una vez que se invoca el método onActivityResult , obtiene el contacto seleccionado por el usuario, busca el número de contacto y lo establece en el cuadro de texto.

  • Ejecute la aplicación y asegúrese de que todo esté bien. El aspecto final de la aplicación de muestra de intención es como se muestra a continuación,

  • Ahora, configure la intención de espresso en el archivo gradle de la aplicación como se muestra a continuación,

dependencies {
   // ...
   androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1'
}
  • Haga clic en la opción de menú Sincronizar ahora proporcionada por Android Studio. Esto descargará la biblioteca de prueba de intención y la configurará correctamente.

  • Abrir ExampleInstrumentedTest.java archivo y añadir el IntentsTestRule en lugar de utilizado normalmente AndroidTestRule . IntentTestRule es una regla especial para manejar las pruebas de intención.

public class ExampleInstrumentedTest {
   // ... code
   @Rule
   public IntentsTestRule<MainActivity> mActivityRule =
   new IntentsTestRule<>(MainActivity.class);
   // ... code
}
  • Agregue dos variables locales para configurar el número de teléfono de prueba y el nombre del paquete del marcador como se muestra a continuación,

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
}
  • Solucione los problemas de importación utilizando la opción Alt + Enter proporcionada por Android Studio o, de lo contrario, incluya las siguientes declaraciones de importación,

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.*;
  • Agregue el siguiente caso de prueba para probar si el marcador se llama correctamente,

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
}

Aquí, los comparadores hasAction , hasData y toPackage se utilizan junto con allOf matcher para tener éxito solo si se pasan todos los comparadores.

  • Ahora, ejecute ExampleInstrumentedTest a través del menú de contenido en Android Studio.

la intención ()

Espresso proporciona un método especial: la intención () de simular una acción de intención externa. intending () acepta el nombre del paquete de la intención que se va a simular y proporciona un método respondWith para establecer cómo se debe responder la intención simulada como se especifica a continuación,

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

Aquí, respondWith () acepta el resultado de la intención de tipo Instrumentation.ActivityResult . Podemos crear una nueva intención de código auxiliar y establecer manualmente el resultado como se especifica a continuación,

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

El código completo para probar si una aplicación de contacto se abre correctamente es el siguiente:

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

Aquí, hemos creado una nueva intención y establecemos el valor de retorno (al invocar la intención) como la primera entrada de la lista de contactos, contenido: //com.android.contacts/data/1 . Luego, hemos establecido el método de intención para simular la intención recién creada en lugar de la lista de contactos. Establece y llama a nuestro intent recién creado cuando se invoca el paquete com.android.contacts y se devuelve la primera entrada predeterminada de la lista. Luego, activamos la acción click () para iniciar el intento simulado y finalmente verificamos si el número de teléfono que invoca el intento simulado y el número de la primera entrada en la lista de contactos son los mismos.

Si falta algún problema de importación, solucione esos problemas de importación utilizando la opción Alt + Enter proporcionada por Android Studio o, de lo contrario, incluya las siguientes declaraciones de importación,

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

Agregue la siguiente regla en la clase de prueba para otorgar permiso para leer la lista de contactos:

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

Agregue la siguiente opción en el archivo de manifiesto de la aplicación, AndroidManifest.xml -

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

Ahora, asegúrese de que la lista de contactos tenga al menos una entrada y luego ejecute la prueba usando el menú contextual de Android Studio.