Cambio de configuración con Robolectric

Para conservar mis AsyncTasks a través de los cambios de configuración, utilizo una solución basada en fragmentos con setRetainInstance (true), que aloja cada AsyncTask y llama de vuelta a una actividad de escucha, similar a esta solución http://www.androiddesignpatterns.com/2013/04 /retaining-objects-across-config-changes.html

En última instancia, el propósito es probar la funcionalidad de retención de AsyncTask durante los cambios de configuración usando Robolectric, pero necesito comenzar con la configuración del cambio de configuración real correctamente. Sin embargo, parece que no puedo imitar el comportamiento de referencia exacta que se produce durante un cambio de configuración.


Aplicación real: al ejecutar una aplicación real, al cambiar la configuración, la actividad se destruye y se vuelve a crear mientras se mantiene el fragmento, por lo que parece estar funcionando. Puedo ver esto revisando sus referencias antes y después del cambio de configuración (ejemplos de referencias que se utilizan a continuación):

  • Real app, before: Actividad: abc Fragmento: xyz

  • Real app, after: Actividad: bca Fragmento: xyz (correctamente retenido y reatado)


Caso 1: Sin embargo, al ejecutar recreate () en la Actividad en la prueba Robolectric, la Actividad no parece tener su instancia correctamente recreada (a pesar de que los documentos dicen que el método realiza todas las llamadas del ciclo de vida):

mActivityController = Robolectric.buildActivity(AsyncTaskTestActivity.class).attach().create().start().resume().visible(); mActivity = mActivityController.get(); mActivity.recreate(); 
  • Robolectric con recreate (), antes: Actividad: abc Fragmento: xyz

  • Robolectric con recreate (), después de Actividad: abc Fragmento: xyz

Esto me lleva a creer que una nueva instancia de actividad no se crea correctamente y la funcionalidad de reinserción por lo tanto no ha ocurrido de una manera real.


Caso 2: Si creo la prueba basada en llamadas de ciclo de vida individuales en su lugar:

 mActivityController = Robolectric.buildActivity(AsyncTaskTestActivity.class).attach().create().start().resume().visible(); mActivityController.pause().stop().destroy(); mActivityController = Robolectric.buildActivity(AsyncTaskTestActivity.class).attach().create().start().resume().visible(); 

En esta versión, parece que la Actividad se reemplaza completamente desde cero, pero también lo hace el Fragmento:

  • Roboelectric con llamadas de ciclo de vida separadas, antes de Actividad: abc Fragmento: xyz

  • Roboeléctrico con llamadas de ciclo de vida separadas, después de Actividad: bca Fragmento: yzx


Parece que estoy reutilizando la misma Actividad (caso 1) o reemplazando todo con nuevas instancias, como si no hay ninguna Aplicación subyacente que retiene el Fragmento (caso 2).

Pregunta: ¿hay alguna manera de configurar mi prueba Robolectric para imitar el resultado de referencia que obtengo al ejecutar la aplicación en un entorno Android real (según el resultado de la aplicación Real), o estoy atascado con la creación de una aplicación de prueba independiente O establecerse con pruebas funcionales Robotium? Intenté hacerlo como este https://stackoverflow.com/a/26468296 pero conseguí el mismo resultado que mi caso 2.

¡Gracias por adelantado!

He jugado alrededor de un pedacito y he venido para arriba con una solución usando Robolectric 3.0 y Mockito:

 @RunWith(RobolectricGradleTestRunner.class) @Config(constants = BuildConfig.class, sdk = Build.VERSION_CODES.KITKAT, shadows = {ExampleActivityTest.ExampleActivityShadow.class}) public class ExampleActivityTest { @Mock private FragmentManager fragmentManagerMock; @Before public void setup() { initMocks(this); setupFragmentManagerMock(); } @Test public void testRestoreAfterConfigurationChange() { // prepare ActivityController<ExampleActivity> controller = Robolectric.buildActivity(ExampleActivity.class); ExampleActivity activity = controller.get(); ExampleActivityShadow shadow = (ExampleActivityShadow) Shadows.shadowOf(activity); shadow.setFragmentManager(fragmentManagerMock); ActivityController<ExampleActivity> controller2 = Robolectric.buildActivity(ExampleActivity.class); ExampleActivity recreatedActivity = controller2.get(); ExampleActivityShadow recreatedActivityShadow = (ExampleActivityShadow) Shadows.shadowOf(recreatedActivity); recreatedActivityShadow.setFragmentManager(fragmentManagerMock); // run & verify controller.create().start().resume().visible(); activity.findViewById(R.id.inc_button).performClick(); activity.findViewById(R.id.inc_button).performClick(); assertEquals(2, activity.lostCount.count); assertEquals(2, activity.retainedCount.count); Bundle bundle = new Bundle(); controller.saveInstanceState(bundle).pause().stop().destroy(); controller2.create(bundle).start().restoreInstanceState(bundle).resume().visible(); assertEquals(0, recreatedActivity.lostCount.count); assertEquals(2, recreatedActivity.retainedCount.count); } private void setupFragmentManagerMock() { final HashMap<String, Fragment> fragments = new HashMap<>(); doAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { return fragments.get(invocation.getArguments()[0]); } }).when(fragmentManagerMock).findFragmentByTag(anyString()); final HashMap<String, Fragment> fragmentsToBeAdded = new HashMap<>(); final FragmentTransaction fragmentTransactionMock = mock(FragmentTransaction.class); doAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { fragmentsToBeAdded.put((String) invocation.getArguments()[1], (Fragment) invocation.getArguments()[0]); return fragmentTransactionMock; } }).when(fragmentTransactionMock).add(any(Fragment.class), anyString()); doAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { fragments.putAll(fragmentsToBeAdded); return null; } }).when(fragmentTransactionMock).commit(); when(fragmentManagerMock.beginTransaction()).thenReturn(fragmentTransactionMock); } @Implements(Activity.class) public static class ExampleActivityShadow extends ShadowActivity { private FragmentManager fragmentManager; @Implementation public FragmentManager getFragmentManager() { return fragmentManager; } public void setFragmentManager(FragmentManager fragmentManager) { this.fragmentManager = fragmentManager; } } } 

Tenga en cuenta que sólo me he burlado de los métodos de FragmentManager ( beginTransaction() y findFragmentByTag() ) y FragmentTransaction ( add() y commit() ) que utilizo en mi código, por lo que es posible que tenga que expandirlos dependiendo de su código.

No he hecho demasiado trabajo con Robolectric todavía, así que puede haber una solución más elegante a esto, pero esto trabaja para mí por ahora.

Puede ver el código fuente completo y la configuración del proyecto aquí: https://github.com/rgeldmacher/leash (podría valer la pena si todavía necesita retener objetos;))

  • Configurar la carpeta de prueba para las pruebas unitarias en el estudio de Android
  • No se puede usar FakeHttpLayer de Robolectric (NullPointerException al llamar a getFakeHttpLayer)
  • Robolectric, Problemas con los elementos de la lista de clics
  • Robolectric no utiliza ShadowWebView como la superclase de una clase que extiende WebView. MustOverrideException en su lugar
  • Android Robolectric y elementos decorativos vectoriales
  • No se puede localizar un Java Runtime Android Studio Robolectric
  • ¿Robolectric soporta el nivel API?
  • Robolectric ContentProvider testing
  • shadowOf () undefined en Robolectric-3.0-rc3.jar
  • Mocking SQLite-Database durante la prueba de actividad con Robolectric
  • Java.lang.ClassNotFoundException: android.R
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.