如何在单元测试中创建一个 Bundle

11

我想测试一个与Bundles有关的方法。然而在测试环境中,我无法创建一个(非空)Bundle对象。

给定以下代码:

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

containsKey 是在应用程序上下文中执行时为 true,但在单元测试中执行时为 false

Service Context

Test Context

我无法弄清楚为什么会这样,以及如何为我的测试创建一个 Bundle。

你尝试过使用模拟框架来创建Bundle吗?虽然它为空是很奇怪的。 - OneCricketeer
4个回答

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

If your build script contains something like this:


testOptions {
    unitTests.returnDefaultValues = true
}

这就是为什么即使您没有为Bundle类指定mock,您的测试也不会失败的原因。

有几种方法可以解决这个问题:

  1. 使用Mockito mocking框架来mock Bundle类。不幸的是,您必须自己编写大量样板代码。例如,您可以使用此方法mock一个bundle对象,以便它通过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;
  }
  • 使用 Robolectric 框架,它提供了一些用于单元测试的影子类。这允许您在单元测试中使用 Android 特定的类,并且它们将正常工作。通过使用该框架,您的单元测试几乎不需要进行任何更改即可正常运行。

  • 这可能是你最不想听到的建议,但也是一个可行的方法。您可以使您的测试功能化并在您的 Android 设备或模拟器上运行。我不建议这种方式,因为速度很慢。在执行测试之前,您必须构建一个测试 apk、安装它并运行。如果您要进行 TDD,这非常缓慢。


  • 1
    我来这里是因为我太懒了,不想自己写那个模拟。 :) - fgysin
    2
    为了完整性,我还不得不添加`when(bundle.keySet()).then(new Answer<Set<String>>() { @Override public Set answer(InvocationOnMock invocation) { return fakeBundle.keySet(); } });`,以防您想测试迭代包的键集的内容。 - Nom1fan
    嗨!我没有找到任何库或GH,所以我刚刚创建了这个小的回购来帮助其他可能需要它的人,受你的答案(@andrei_zaitcev)的启示。这是这个:https://github.com/LluisFelip/MockBundle这是我第一次在GH上做出公共贡献,因此如果有人对如何改进它或其他事情有建议,我会非常乐意知道 :) - Archison
    2
    这里有一个Java的完整可用的BundleMock文件示例: https://github.com/konmik/nucleus/blob/master/nucleus-test-kit/src/main/java/mocks/BundleMock.java 只需将该文件复制到您的测试文件夹中并使用即可,它能正常工作。 - Boris Gitlin
    @BorisGitlin 把这个作为答案发布。我会点赞的。 - Shishir Shetty
    @Shishir Shetty - 完成 - Boris Gitlin

    2
    使用Mockito,您可以做到以下事情:

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

    containsKey现在将在您正在测试的类中为true。

    关于when()的一些额外信息。当您使用when()包装模拟对象方法调用时,它将跳过调用该方法的实际实现,并立即返回thenReturn()部分提供的值。这对编写某些单元测试非常有用,因为一旦一个方法调用进入其兔子洞中,就可能会变成噩梦。

    这是我发现的设置通往您实际想要测试的代码路径的最佳方式。


    1
    我最终使用了Mockito:
    Bundle extras = mock(Bundle.class);
    

    由于我不需要测试在bundle中传递的许多参数,所以我像这样模拟对它们的调用:

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

    我向团队提出的建议是用Java类替换Bundle的使用。

    1

    以下是一种使用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
    }
    

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