android - 使用 Robolectric 更改配置

标签 android android-fragments android-lifecycle robolectric android-testing

为了跨配置更改保留我的 AsyncTasks,我使用基于 fragment 的解决方案和 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 实例没有正确创建,因此重新附加功能并没有以真实的方式发生。


情况 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://stackoverflow.com/a/26468296但得到了与案例 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 (如果您仍然需要保留对象,可能值得一看;))

关于android - 使用 Robolectric 更改配置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29085893/

相关文章:

android - 在 Fragment 中以编程方式添加 TextView 并设置为 mainLayout

android - SearchView 不显示在带有嵌套 fragment 的工具栏上

android - 我应该删除 onDestroyView 中的 OnClickListener 吗?

android - 一项 Activity 和所有其他 fragment

android - 从 Android Studio 启动 AVD 时出错

java - Android:数据库操作耗电吗?

java - fragment 元素中的 fragment onClick 方法

android - 检测从 backstack 弹出时 fragment 在 OnResume 中是否真正可见

android - SQLite in operator in query()

android - 如何在 Android 中使用图案/密码模式锁定/解锁屏幕?