Cómo utilizar Espresso Idling Resource para llamadas de red
Estoy tratando de usar Espresso para probar mi interfaz de usuario. Cuando entro en mi aplicación, hago una llamada a Parse API (llamada de red) para verificar el nombre de usuario y la contraseña. Si todo está bien, el usuario se dirige a una nueva actividad. Quiero probar esto, pero no puedo parece que funcione con el recurso de recursos inactivos.
Código:
- Actividad de inicio para pruebas
- Android Espresso: el filtrado de prueba no es compatible con la versión dada de JUnit. Actualice la versión de JUnit por lo menos a 4.6
- Configurar gradle para usar JUnit, Mockito, Hamcrest y Espresso
- Prueba de la barra de progreso en Android con Espresso
- Mientras se ejecuta una prueba de Espresso utilizando el estudio de Android. Obtuve el siguiente error
public class ApplicationTest extends ActivityInstrumentationTestCase2<LoginActivity> { private CountingIdlingResource fooServerIdlingResource; public ApplicationTest() { super(LoginActivity.class); } @Before public void setUp() throws Exception { super.setUp(); injectInstrumentation(InstrumentationRegistry.getInstrumentation()); getActivity(); CountingIdlingResource countingResource = new CountingIdlingResource("FooServerCalls"); this.fooServerIdlingResource = countingResource; Espresso.registerIdlingResources(countingResource); } public void testChangeText_sameActivity() { // Type text and then press the button. onView(withId(R.id.username)) .perform(typeText("[email protected]"), closeSoftKeyboard()); onView(withId(R.id.password)) .perform(typeText("s"), closeSoftKeyboard()); if(performClick()) onView(withId(R.id.main_relative_layout)) .check(matches(isDisplayed())); // Check that the text was changed. } public boolean performClick(){ fooServerIdlingResource.increment(); try { onView(withId(R.id.login)).perform(click()); return true; } finally { fooServerIdlingResource.decrement(); } } @SuppressWarnings("javadoc") public final class CountingIdlingResource implements IdlingResource { private static final String TAG = "CountingIdlingResource"; private final String resourceName; private final AtomicInteger counter = new AtomicInteger(0); private final boolean debugCounting; // written from main thread, read from any thread. private volatile ResourceCallback resourceCallback; // read/written from any thread - used for debugging messages. private volatile long becameBusyAt = 0; private volatile long becameIdleAt = 0; /** * Creates a CountingIdlingResource without debug tracing. * * @param resourceName the resource name this resource should report to Espresso. */ public CountingIdlingResource(String resourceName) { this(resourceName, false); } /** * Creates a CountingIdlingResource. * * @param resourceName the resource name this resource should report to Espresso. * @param debugCounting if true increment & decrement calls will print trace information to logs. */ public CountingIdlingResource(String resourceName, boolean debugCounting) { this.resourceName = checkNotNull(resourceName); this.debugCounting = debugCounting; } @Override public String getName() { return resourceName; } @Override public boolean isIdleNow() { return counter.get() == 0; } @Override public void registerIdleTransitionCallback(ResourceCallback resourceCallback) { this.resourceCallback = resourceCallback; } /** * Increments the count of in-flight transactions to the resource being monitored. * <p/> * This method can be called from any thread. */ public void increment() { int counterVal = counter.getAndIncrement(); if (0 == counterVal) { becameBusyAt = SystemClock.uptimeMillis(); } if (debugCounting) { Log.i(TAG, "Resource: " + resourceName + " in-use-count incremented to: " + (counterVal + 1)); } } /** * Decrements the count of in-flight transactions to the resource being monitored. * <p/> * If this operation results in the counter falling below 0 - an exception is raised. * * @throws IllegalStateException if the counter is below 0. */ public void decrement() { int counterVal = counter.decrementAndGet(); if (counterVal == 0) { // we've gone from non-zero to zero. That means we're idle now! Tell espresso. if (null != resourceCallback) { resourceCallback.onTransitionToIdle(); } becameIdleAt = SystemClock.uptimeMillis(); } if (debugCounting) { if (counterVal == 0) { Log.i(TAG, "Resource: " + resourceName + " went idle! (Time spent not idle: " + (becameIdleAt - becameBusyAt) + ")"); } else { Log.i(TAG, "Resource: " + resourceName + " in-use-count decremented to: " + counterVal); } } checkState(counterVal > -1, "Counter has been corrupted!"); } /** * Prints the current state of this resource to the logcat at info level. */ public void dumpStateToLogs() { StringBuilder message = new StringBuilder("Resource: ") .append(resourceName) .append(" inflight transaction count: ") .append(counter.get()); if (0 == becameBusyAt) { Log.i(TAG, message.append(" and has never been busy!").toString()); } else { message.append(" and was last busy at: ") .append(becameBusyAt); if (0 == becameIdleAt) { Log.w(TAG, message.append(" AND NEVER WENT IDLE!").toString()); } else { message.append(" and last went idle at: ") .append(becameIdleAt); Log.i(TAG, message.toString()); } } } }
}
La excepción que recibo ahora es la siguiente:
ndroid.support.test.espresso.IdlingResourceTimeoutException: Wait for [FooServerCalls] to become idle timed out
Cuando ejecuto la prueba, el nombre de usuario y la contraseña se están llenando pero el clic nunca se llama y recibo la excepción después de unos segundos. ¿Cómo debo implementar el recurso inactivo correctamente?
EDITAR –
Yo recomendaría usar Calabash para Android. Calabash funciona de forma similar pero no necesita que cambie el código de su aplicación para realizar pruebas.
- Prueba de un ViewPager y TabPageIndicator con Espresso
- TestUI (Jenkins) utilizando espresso
- Android: Cómo usar IdlingResource para esperar las tareas de fondo (Espresso)
- Cómo usar Espresso para probar el elemento en el adaptador en una posición específica
- Conflicto con dependencia 'com.android.support:support-annotations'. Las versiones resueltas para la aplicación (23.1.0) y la aplicación de prueba (23.0.1) difieren
- ¿Hay una manera de ejecutar la prueba de Espresso con múltiples métodos de prueba pero sólo un método de configuración?
- Manera correcta de usar IdlingResource en Espresso Android
- Snackbar y Espresso fallando a veces
Como la otra respuesta sugiere, el countingIdlingResource realmente no se aplica para su caso de uso.
Lo que siempre hago es añadir una interfaz – vamos a llamar a este ProgressListener
– como un campo de la actividad / fragmento que tiene un recurso que se espera (trabajo de fondo asíncrono, más sesiones de redes, etc) y un método para notificarlo cada vez El progreso es mostrado o despedido.
Estoy asumiendo que usted tiene su lógica de validación de credenciales y la llamada a la Parse API en la LoginActivity
, que luego llamará a una intención a MainActivity
si tiene éxito.
public class LoginActivity extends AppCompatActivity { private ProgressListener mListener; ... public interface ProgressListener { public void onProgressShown(); public void onProgressDismissed(); } public void setProgressListener(ProgressListener progressListener) { mListener = progressListener; } ... public void onLoginButtonClicked (View view) { String username = mUsername.getText().toString(); String password = mPassword.getText().toString(); // validate credentials for blanks and so on // show progress and call parse login in background method showProgress(); ParseUser.logInInBackground(username,password, new LogInCallback() { @Override public void done(ParseUser parseUser, ParseException e) { dismissProgress(); if (e == null){ // Success!, continue to MainActivity via intent Intent intent = new Intent (LoginActivity.this, MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent); } else { // login failed dialog or similar. } } }); } private void showProgress() { // show the progress and notify the listener ... notifyListener(mListener); } private void dismissProgress() { // hide the progress and notify the listener ... notifyListener(mListener); } public boolean isInProgress() { // return true if progress is visible } private void notifyListener(ProgressListener listener) { if (listener == null){ return; } if (isInProgress()){ listener.onProgressShown(); } else { listener.onProgressDismissed(); } } }
A continuación, simplemente implementar la clase IdlingResource y reemplazar sus métodos para comunicarse cuando el recurso va de ocupado a inactivo a través de su ResourceCallBack
public class ProgressIdlingResource implements IdlingResource { private ResourceCallback resourceCallback; private LoginActivity loginActivity; private LoginActivity.ProgressListener progressListener; public ProgressIdlingResource(LoginActivity activity){ loginActivity = activity; progressListener = new LoginActivity.ProgressListener() { @Override public void onProgressShown() { } @Override public void onProgressDismissed() { if (resourceCallback == null){ return ; } //Called when the resource goes from busy to idle. resourceCallback.onTransitionToIdle(); } }; loginActivity.setProgressListener (progressListener); } @Override public String getName() { return "My idling resource"; } @Override public boolean isIdleNow() { // the resource becomes idle when the progress has been dismissed return !loginActivity.isInProgress(); } @Override public void registerIdleTransitionCallback(ResourceCallback resourceCallback) { this.resourceCallback = resourceCallback; } }
El último paso es registrar su recurso de setUp()
personalizado en el método setUp()
la prueba:
Espresso.registerIdlingResources(new ProgressIdlingResource((LoginActivity) getActivity()));
¡Y eso es! Ahora espresso esperará a que se complete su proceso de inicio de sesión y luego continúe con todas las otras pruebas.
Por favor, hágamelo saber si no estaba lo suficientemente claro o si eso es exactamente lo que necesitaba.
Espresso examinará el recurso inactivo justo antes de realizar el clic (o cualquier acción de vista). Pero no disminuye su contador hasta después del clic. Es un punto muerto.
No veo ninguna solución rápida aquí; Su enfoque realmente no tiene sentido para mí. Algunos enfoques alternativos posibles vienen a la mente:
- Dependiendo de la biblioteca que utilice para la creación de redes, es posible que pueda escribir un recurso inactivo que compruebe si hay una llamada en curso.
- Si muestra un indicador de progreso mientras la llamada de inicio de sesión está en curso, podría instalar un IdlingResource que espera a que desaparezca.
- Podrías esperar a que se inicie la siguiente actividad o que aparezca o desaparezca una determinada vista.
Otro enfoque es tener un recurso inactivo personalizado que pueda examinar su actividad. He creado uno aquí:
public class RequestIdlingResource implements IdlingResource { private ResourceCallback resourceCallback; private boolean isIdle; @Override public String getName() { return RequestIdlingResource.class.getName(); } @Override public boolean isIdleNow() { if (isIdle) return true; Activity activity = getCurrentActivity(); if (activity == null) return false; idlingCheck(activity); if (isIdle) { resourceCallback.onTransitionToIdle(); } return isIdle; } private Activity getCurrentActivity() { final Activity[] activity = new Activity[1]; java.util.Collection<Activity> activities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED); activity[0] = Iterables.getOnlyElement(activities); return activity[0]; } @Override public void registerIdleTransitionCallback( ResourceCallback resourceCallback) { this.resourceCallback = resourceCallback; } public void idlingCheck(Activity activity) { /* Look up something (view or method call) on the activity to determine if it is idle or busy */ } }
https://gist.github.com/clivejefferies/2c8701ef70dd8b30cc3b62a3762acdb7
Tengo la inspiración de aquí, que muestra cómo se podría utilizar en una prueba:
Lo bueno es que no tiene que agregar ningún código de prueba a su clase de implementación.
- Android: Utilice el icono como botón de retroceso sin volver a cargar la actividad anterior
- ¿Dónde está el código que se llama cuando se llama getApplicationContext ()?