如何在单元测试中创建 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。
有几个选项可以解决这个问题:
使用 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;
}
使用 Robolectric 框架为您的单元测试提供某种影子 classes。这允许您在单元测试中使用 Android 特定的 classes,它们将正常运行。通过使用该框架,您的单元测试几乎可以正常运行,而无需您进行任何更改。
你最不喜欢的,我想,但是,它是合格的。您可以在 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
}
我想测试一个处理 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。
有几个选项可以解决这个问题:
使用 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; }
使用 Robolectric 框架为您的单元测试提供某种影子 classes。这允许您在单元测试中使用 Android 特定的 classes,它们将正常运行。通过使用该框架,您的单元测试几乎可以正常运行,而无需您进行任何更改。
你最不喜欢的,我想,但是,它是合格的。您可以在 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
}