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:

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.

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:

https://github.com/AzimoLabs/ConditionWatcher/blob/master/sample/src/androidTest/java/com/azimolabs/f1sherkk/conditionwatcherexample/IdlingResourceExampleTests.java

Lo bueno es que no tiene que agregar ningún código de prueba a su clase de implementación.

  • Gradle, Robolectric y Espresso
  • ¿Qué es el modo táctil y por qué es importante para la clase * ActivityTestRule *?
  • Java.lang.IncompatibleClassChangeError al incluir espresso-contrib: 2.0
  • Espresso inyectando un objeto simulador presentador
  • Espresso NestedScrollView
  • Android Espresso vs Actionbar
  • Cómo leer el archivo json de la carpeta de activos cuando se prueba por Espresso?
  • Uso de Espresso para probar cambios
  • Android Espresso - Haga clic en la casilla de verificación si no está marcada
  • Java.lang.IncompatibleClassChangeError causado por la instanciación de ActivityTestRule
  • openDrawer de espresso contrib está obsoleto
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.