使用Robolectric进行配置更改

6
为了在配置更改时保留我的AsyncTasks,我使用基于片段的解决方案并使用setRetainInstance(true)来托管每个AsyncTask,并回调到一个监听Activity,类似于这个解决方案 http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
最终目的是使用Robolectric测试AsyncTask的保留功能是否在配置更改期间正常工作,但我需要先正确设置实际的配置更改。然而,似乎我无法模仿发生在配置更改期间的确切参考行为。

真实应用:在运行真实应用时,当配置更改时,Activity会被销毁并重新创建,而Fragment则会被保留,因此它似乎正常工作。我可以通过在配置更改之前和之后检查它们的引用(以下是使用的示例引用)来看到这一点:

  • 真实应用,在之前: Activity:abc Fragment:xyz

  • 真实应用,在之后: Activity:bca Fragment:xyz(正确保留和重新附加)


案例1: 在Robolectric测试中运行Activity的recreate()方法时,尽管文档称该方法执行所有生命周期调用,但Activity似乎没有正确重新创建其实例。
mActivityController =
Robolectric.buildActivity(AsyncTaskTestActivity.class).attach().create().start().resume().visible();

mActivity = mActivityController.get();
mActivity.recreate();
  • 使用recreate()的Robolectric,之前: Activity: abc Fragment: xyz

  • 使用recreate()的Robolectric,之后: Activity: abc Fragment: xyz

这让我相信一个新的Activity实例没有被正确创建,因此reattachment功能并没有真正发生。


案例2:如果我基于单个生命周期调用创建测试:
mActivityController = Robolectric.buildActivity(AsyncTaskTestActivity.class).attach().create().start().resume().visible();
mActivityController.pause().stop().destroy();
mActivityController = Robolectric.buildActivity(AsyncTaskTestActivity.class).attach().create().start().resume().visible();

在这个版本中,似乎Activity被完全从头开始替换,Fragment也是如此:
  • 使用具有单独生命周期调用的Robolectric,在Activity: abc之前, Fragment: xyz

  • 使用具有单独生命周期调用的Robolectric,在Activity: bca之后, Fragment: yzx


看起来我要么重复使用相同的Activity(情况1),要么用新实例替换所有内容,就像没有维护Fragment的基础应用程序一样(情况2)。

问题:是否有任何方法可以设置我的Robolectric测试以模拟在实际Android环境中运行应用程序时获得的参考结果(根据真实应用程序结果),或者我必须创建一个单独的测试应用程序或接受Robotium功能测试?我尝试这样做https://dev59.com/2G025IYBdhLWcg3wVkif#26468296但得到了与我的情况2相同的结果。

提前致谢!


我遇到了完全相同的问题。你找到解决方案了吗? - ct_rob
1个回答

2

我玩了一下,使用Robolectric 3.0和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;
        }
    }
}

请注意,我只模拟了FragmentManager(beginTransaction()findFragmentByTag())和FragmentTransaction(add()commit())中我在代码中使用的方法,因此根据您的代码可能需要扩展这些内容。
我还没有太多使用Robolectric的经验,所以可能有更优雅的解决方案,但现在这对我来说可以工作。
如果您仍然需要保留对象,可以在此处查看完整的源代码和项目设置:https://github.com/rgeldmacher/leash

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接