【问题标题】:How do you force a configuration change in an Android Robolectric test?如何在 Android Robolectric 测试中强制更改配置?
【发布时间】:2011-05-19 18:53:22
【问题描述】:

我正在使用 robolectric 来使我的 android 单元测试足够快,以使其有用。我想在屏幕方向发生变化以模拟常见的现实世界用例时测试我编写的代码是否有效。

具体来说,我正在测试的是对服务器的异步 http 调用,并在获取结果后解析了一些 xml。我对所有工作都进行了单元测试,但无法弄清楚如何模拟屏幕旋转。任何导致 Activity 重新创建自身的状态更改都可以,不一定是屏幕旋转。

使用模拟器的解决方案不是一个选项,因为我每分钟运行几次测试并且它们必须在 2 秒内运行。如果可能的话,我也希望它能够与 roboguice 一起使用。

谢谢。

【问题讨论】:

  • 我正在这样做。不确定它是否有效:setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

标签: android unit-testing screen-orientation robolectric


【解决方案1】:

在 Robolectric 中调用 recreate(请注意,使用 Robolectric 时不必担心旧的 API 版本)非常接近于模拟配置更改,但不一定能捕捉到您可能犯的所有错误。特别是它不会创建 Activity 的新实例(而且我很确定它不会“擦洗”它),因此如果您忘记恢复 Activity 的成员字段,您的测试将无法捕捉到这一点。不过,它确实可以很好地测试片段(未保留的片段被销毁并重新实例化)。

如果您在 Robolectric 测试中对 Activity 调用 recreate,则会发生以下情况:

  1. onSaveInstanceState
  2. 暂停
  3. onStop
  4. onDestroy
  5. 在创建时
  6. onStart
  7. onRestoreInstanceState
  8. onResume

(我通过在测试活动中覆盖大多数生命周期方法并将日志语句放入其中发现了这一点)

您可以使用如下代码更接近真实的配置更改:

Bundle bundle = new Bundle();
activityController.saveInstanceState(bundle).pause().stop().destroy();
controller = Robolectric.buildActivity(YourActivity.class).create(bundle).start().restoreInstanceState(bundle).resume();
activity = controller.get();

(此代码适用于 Robolectric 2.1 - 如果您使用的是 2.2 或更高版本,您可能需要在 .resume() 之后调用 .visible()

使用上面的内容,您将看到以下事件发生:

  1. onSaveInstanceState
  2. 暂停
  3. onStop
  4. onDestroy
  5. 已实例化新的 Activity 实例(所有后续调用都在此新实例上)
  6. 在创建时
  7. onStart
  8. onRestoreInstanceState
  9. onResume
  10. onPostResume

这仍然不是完全匹配,但更接近于遇到真正的配置更改时会发生的情况。

我认为这可能是对由于内存不足而破坏活动时发生的情况的一个不错的模拟,因为与调用 recreate() 不同,我认为这不会保留对保留片段的引用。不过,我在这方面的情况很不稳定!

更新:

如果您的 Activity 是通过 Intent 启动的,您可能需要添加对 withIntent 的调用,如下所示:

Robolectric.buildActivity(YourActivity.class).withIntent(intent).create(bundle) // and so on...

【讨论】:

  • 很酷,但是如何抓包呢?我发现的最好方法是调用 onSaveInstanceState( b ) 并让它填写 b (这是我传入的包)
  • 是的,这是正确的方法 - 查看我答案中的第一个代码块
  • 哦,我现在看到了。感谢 ZoFreX
  • 很好的答案,你是对的;使用此方法不会重新附加保留的片段。然而,通过一点模仿魔法,我们也可以做到这一点:stackoverflow.com/questions/29085893/…
【解决方案2】:

您要针对哪个 Android API 级别进行编译?如果是 3.0 或更高版本,您可以尝试Activity.recreate()。文档指出:

使用新实例重新创建此 Activity。这导致与由于配置更改而创建 Activity 时的流程基本相同——当前实例将经历其生命周期到 onDestroy() 并在其后创建一个新实例。

我自己没试过。

【讨论】:

  • 这是一个好主意,但不应该是公认的答案,因为它不能完全回答问题。 ZoFrX 和 Sudocoder 提供了更灵活、更完整的解决方案。
  • 我试过了。但是,它不会 100% 重复配置更改。
  • 现在最好使用 AndroidX 中的ActivityCompat.recreate(Activity) 来解决平台错误。
【解决方案3】:

我已经成功使用 ZoFreX 的答案,但是我想添加如何实际模拟旋转。我知道 OP 指定轮换不是绝对必须的,但标题暗示这应该包含在答案中,并且可以帮助最终迷路的人。

基本上,在应用 ZoFrex 的解决方案之前设置活动的方向。或者更简洁的代码:

// toggle orientation
int currentOrientation = fragment.getActivity().getResources().getConfiguration().orientation;
boolean isPortraitOrUndefined = currentOrientation == Configuration.ORIENTATION_PORTRAIT || currentOrientation == Configuration.ORIENTATION_UNDEFINED;
int toOrientation = isPortraitOrUndefined ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT;
Robolectric.application.getResources().getConfiguration().orientation = toOrientation;

// ZoFreX's solution
Bundle bundle = new Bundle();
activityController.saveInstanceState(bundle).pause().stop().destroy();
controller = Robolectric.buildActivity(YourActivity.class).create(bundle).start().restoreInstanceState(bundle).resume();
activity = controller.get();

请查看 ZoFreX 的解决方案,因为它包含此处未包含的其他信息。

【讨论】:

  • 很好看,我忘了他们想模拟实际的旋转。不错的解决方案:)
【解决方案4】:

Robolectric 的 ActivityController 类有一个 configurationChange() 方法可能会处理这个问题。见鬼,它甚至还有 javadoc 注释! :D

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多