如何在单元测试中创建 Bundle

How to create a Bundle in a Unit test

我想测试一个处理 Bundles 的方法。不过,我无法在测试环境中创建 (non-null) Bundle 对象。

给定以下代码:

Bundle bundle = new Bundle();
bundle.putString("key", "value");
boolean containsKey = bundle.containsKey("key");

containsKey 如果代码在应用程序上下文中执行,则为 true,但如果在单元测试中执行,则为 false

我不明白为什么会这样,也不知道如何为我的测试创建 Bundle。

如果您的构建脚本包含如下内容:

testOptions {
    unitTests.returnDefaultValues = true
}

这就是为什么即使您没有为 Bundle 指定模拟也不会失败的原因 class。

有几个选项可以解决这个问题:

  1. 使用 Mockito 模拟框架来模拟 Bundle class。不幸的是,您必须自己编写大量样板代码。例如,您可以使用此方法模拟一个包对象,因此它将 return 通过 getString 方法为您提供正确的值:

     @NonNull
     private Bundle mockBundle() {
           final Map<String, String> fakeBundle = new HashMap<>();
           Bundle bundle = mock(Bundle.class);
           doAnswer(new Answer() {
           @Override
           public Object answer(InvocationOnMock invocation) throws Throwable {
                 Object[] arguments = invocation.getArguments();
                 String key = ((String) arguments[0]);
                 String value = ((String) arguments[1]);
                 fakeBundle.put(key, value);
                 return null;
           }
           }).when(bundle).putString(anyString(), anyString());
           when(bundle.get(anyString())).thenAnswer(new Answer<String>() {
                  @Override
                  public String answer(InvocationOnMock invocation) throws Throwable {
                       Object[] arguments = invocation.getArguments();
                       String key = ((String) arguments[0]);
                       return fakeBundle.get(key);
                   }
           });
           return bundle;
      }
    
  2. 使用 Robolectric 框架为您的单元测试提供某种影子 classes。这允许您在单元测试中使用 Android 特定的 classes,它们将正常运行。通过使用该框架,您的单元测试几乎可以正常运行,而无需您进行任何更改。

  3. 你最不喜欢的,我想,但是,它是合格的。您可以在 android 设备或​​模拟器上使您的测试正常运行并 运行 它。我不推荐那种方式,因为速度。在执行测试之前,您必须构建一个测试 apk,安装它并 运行。如果您要进行 TDD,这会非常慢。

我最终使用了 Mockito:

Bundle extras = mock(Bundle.class);

并且因为我不需要测试在 bundle 中传递的大量参数,所以我像这样模拟了对它们的调用:

private void stubParameterNameExtras(Bundle extras, boolean value) {
    when(extras.getBoolean(KEY, false)).thenReturn(value);
}

我对团队的建议是用 Java 模拟替换 Bundle 的使用。

使用 Mockito 你可以做:

Bundle mockBundle = Mockito.mock(Bundle.class);
Mockito.when(mockBundle.containsKey("key")).thenReturn(true);

containsKey 现在将在您正在测试的 class 中为真。

关于 when() 的一些附加信息。当您使用 when() 包装模拟对象方法调用时,它将跳过调用该方法的实际实现并立即 return thenReturn() 部分中提供的值。这是编写某些单元测试时的救命稻草,因为一旦某个方法调用到它的兔子洞中,它就会变成噩梦。

这是我找到的设置路径以获取您实际想要测试的代码的最佳方法。

这是一种使用 Kotlin 和 mockk 的方法,仿照 bundleOf

private fun mockBundleOf(vararg pairs: Pair<String, Any?>): Bundle {
    val bundle = mockk<Bundle>()

    for ((key, value) in pairs) {
        when (value) {
            is Boolean -> every { bundle.getBoolean(key) } returns value
            is Byte -> every { bundle.getByte(key) } returns value
            is Char -> every { bundle.getChar(key) } returns value
            is Double -> every { bundle.getDouble(key) } returns value
            is Float -> every { bundle.getFloat(key) } returns value
            is Int -> every { bundle.getInt(key) } returns value
            is Long -> every { bundle.getLong(key) } returns value
            is Short -> every { bundle.getShort(key) } returns value
            is String -> every { bundle.getString(key) } returns value
            else -> throw UnsupportedOperationException("Type is not supported.")
        }
    }

    return bundle
}