5

Um meine AsyncTasks über Konfigurationsänderungen behalten, verwende ich eine fragmentbasierte Lösung mit setRetainInstance (true), die jedem AsyncTask Gastgeber und rufe zurück auf Zuhören eine Aktivität ähnlich zu dieser Lösung http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.htmlKonfigurationsänderung Robolectric mit

Letztendlich Der Zweck besteht darin, die Aufbewahrungsfunktionalität der AsyncTask während der gesamten Konfigurationsänderungen mit Robolectric zu testen, aber ich muss mit der korrekten Konfiguration der eigentlichen Konfigurationsänderung beginnen. Es scheint jedoch, dass ich das genaue Referenzverhalten, das während einer Konfigurationsänderung auftritt, nicht nachahmen kann.


Echt App: Wenn eine echte App ausgeführt wird, auf Konfigurationsänderung wird die Aktivität zerstört und neu erstellt, während das Fragment erhalten bleibt, so scheint es, zu arbeiten. Das kann ich sehen, durch ihre Referenzen vor der Prüfung und nach der Konfigurationsänderung (zB Referenzen verwendet unten):

  • Echt App vor: Aktivität: abc Fragment: xyz

  • Real App nach: Aktivität: bca Fragment: xyz (richtig beibehalten und wieder angebracht)


Fall 1: Wenn recreate läuft() auf der Aktivität im Robolectric Test jedoch die Aktivität nicht seine Instanz scheint richtig (trotz der Dokumentation neu erstellt haben, sagt die Methode alle führt der Lifecycle-Anrufe):

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

mActivity = mActivityController.get(); 
mActivity.recreate(); 
  • Robolectric mit recreate(), vor: Aktivität: abc Fragment: xyz

  • Robolectric mit recreate(), nach Aktivität: abc Fragment: xyz

Dies führt mich, dass eine neue Aktivität Instanz zu glauben, ist nicht richtig angelegt und die Reattachment-Funktionalität ist somit nicht real passiert.


Fall 2: Wenn ich erstellen Sie den Test auf einzelne Lebenszyklus basiert fordert stattdessen:

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

In dieser Version ist es die Aktivität scheint voll von Grund auf neu ersetzt wird, aber so auch die Fragment:

  • Robolectric mit separaten Lifecycle Anrufen, bevor Activi ty: abc Fragment: xyz

  • Robolectric mit separaten Lifecycle Anrufen nach Aktivität: bca Fragment: yzx


Es scheint, ich bin entweder die gleiche Aktivität wiederverwenden (Fall 1) oder alles durch neue Instanzen ersetzen, als ob es keine zugrunde liegende Anwendung gibt, die das F beibehält ragment (Fall 2).

Frage: ist es eine Möglichkeit, meinen Robolectric Test einstellen kann, um das Referenzergebnis nachzuahmen, die ich erhalte, wenn die App in einer aktuellen Android-Umgebung ausgeführt wird (wie in dem realen App Ergebnis), oder ist ich fest mit entweder eine separate Test-App erstellen oder mit Robotium-Funktionstests abrechnen? Ich habe versucht, es so zu tun https://stackoverflow.com/a/26468296, aber habe das gleiche Ergebnis wie mein Fall 2.

Vielen Dank im Voraus!

+0

ich genau das gleiche Problem haben. Hast du jemals eine Lösung gefunden? –

Antwort

2

Ich habe um ein bisschen gespielt und kam mit einer Lösung oben mit Robolectric 3.0 und 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; 
     } 
    } 
} 

Bitte beachte, dass ich nur die Methoden der FragmentManager (beginTransaction() und findFragmentByTag()) und FragmentTransaction (add() und commit() verspottet haben), die ich in meinem Code verwende, also müssen Sie diese abhängig von Ihrem Code möglicherweise erweitern.

Ich habe noch nicht zu viel Arbeit mit Robolectric gemacht, also könnte es eine elegantere Lösung dafür geben, aber das funktioniert für mich vorerst.

Sie können den vollständigen Quellcode und Projekt-Setup siehe hier: https://github.com/rgeldmacher/leash (vielleicht einen Blick wert sein, wenn Sie noch Objekte behalten müssen;))

Verwandte Themen