如何在Android Robolectric测试中强制进行配置更改?

21

我正在使用robolectric来加速我的android单元测试以便于实际应用。我想要测试自己编写的代码在屏幕方向改变时是否能正常工作,以模拟常见的真实使用情况。

具体来说,我正在测试一个异步的http调用到一个服务器,并在获取结果后解析一些xml。我已经很好地完成了所有这些单元测试,但是无法弄清楚如何模拟屏幕旋转。任何导致活动重新创建的状态更改都可以,不一定是屏幕旋转。

由于我每分钟运行我的测试多次,因此不能使用模拟器作为解决方案,而且测试必须在2秒内运行。如果可能的话,我也希望这能够与roboguice一起工作。

谢谢。


1
我正在做这个。不确定是否有效:setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); - davidjnelson
4个回答

19

在使用Robolectric时,调用recreate(注意,使用Robolectric时无需担心旧API版本)相当于模拟配置更改,但不一定会捕获您可能发生的所有错误。特别是它不会创建Activity的新实例(我很确定它也不会“清除”它),因此,如果您忘记还原Activity的成员字段,您的测试将无法捕获该错误。但对于测试片段而言,它的效果足够好(非保留片段会被销毁并重新实例化)。

如果在Robolectric测试中调用Activity的recreate,则会发生以下内容:

  1. onSaveInstanceState
  2. onPause
  3. onStop
  4. onDestroy
  5. onCreate
  6. onStart
  7. onRestoreInstanceState
  8. onResume

(我通过在测试Activity中覆盖大多数生命周期方法并在其中放置记录语句来发现这一点)

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

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. onPause
  3. onStop
  4. onDestroy
  5. 实例化 Activity 的新实例(以下所有调用均在此新实例上执行)
  6. onCreate
  7. onStart
  8. onRestoreInstanceState
  9. onResume
  10. onPostResume

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

我认为这可能是当由于低内存而销毁活动时会发生的情况的不错模拟,因为与调用recreate()不同,我认为它不会保留对已保留片段的引用。但在这个领域我还很薄弱!

更新:

如果您的 Activity 是通过intent启动的,您可能需要添加一个withIntent调用,像这样:

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

很酷,但是怎么获取这个bundle呢?我发现最好的方法是调用onSaveInstanceState(b),并让它填充b(即我传入的bundle)。 - sudocoder
是的,那就是正确的方法 - 请查看我回答中的第一个代码块。 - ZoFreX
我现在明白了。谢谢ZoFreX。 - sudocoder
非常好的答案,你是正确的; 使用这种方法不会重新附加保留片段。但是通过一点点的 mockito 魔法,我们也可以使其工作:https://dev59.com/vYjca4cB1Zd3GeqP1svx#31712261 - ct_rob

11
你正在编译什么Android API级别?如果是3.0或以上,你可以尝试使用Activity.recreate()。文档说明如下:

导致此Activity被重建为一个新的实例。这会导致基本上与由于配置更改而创建Activity时相同的流程-当前实例将通过其生命周期到onDestroy(),然后创建一个新实例。

我自己还没有尝试过。

1
这是一个好主意,但不应该被接受为答案,因为它没有完全回答问题。 ZoFreX和Sudocoder提供的解决方案更加灵活和完整。 - sudocoder
我尝试了。但是它不能100%地重现配置更改。 - windkiosk
1
现在最好使用 AndroidX 中的 ActivityCompat.recreate(Activity) 来解决平台错误。 - Milosz Tylenda

10

我使用ZoFreX的答案已经获得了成功,不过我想补充一下如何实际模拟旋转。我知道题主指出旋转并不是必需的,但标题暗示应该在答案中包含,这可以帮助那些最终误入此处的人。

基本上,在应用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 的解决方案,因为它包含了此处未包含的其他信息。

不错,我忘了他们想要模拟实际的旋转。好解决方案 :) - ZoFreX

4

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


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