我想知道是否有任何方法来存根Build.Version.SDK_INT
的值?假设我在ClassUnderTest
中有以下行:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
//do work
}else{
//do another work
}
如何覆盖所有代码?
我的意思是我想用不同的SDK_INT运行两个测试来进入两个块。
是否可以在android 本地单元测试中使用Mockito
/PowerMockito
?
谢谢
使用反射改变值
static void setFinalStatic(Field field, Object newValue) throws Exception {
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, newValue);
}
和
setFinalStatic(Build.VERSION.class.getField("SDK_INT"), 123);
经过测试。作品。
更新:有一种更简洁的方法。
创建接口
interface BuildVersionProvider {
fun currentVersion(): Int
}
实现接口
class BuildVersionProviderImpl : BuildVersionProvider {
override fun currentVersion() = Build.VERSION.SDK_INT
}
每当需要当前构建版本时,通过接口将该类作为构造函数参数注入。然后在测试中创建SUT(被测系统)对象时。您可以自己实现接口。这种方式可能需要更多的代码,但遵循SOLID原则,并提供可测试的代码,而不会混淆反射和系统变量。
作为反射的替代方案,您可以使用自己的类来检查API,然后使用Mockito在快速JVM单元测试中测试依赖API的逻辑。
示例类
import android.os.Build
class SdkChecker {
fun deviceIsOreoOrAbove(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
}
示例测试方法
fun createNotificationChannel(notificationManager: NotificationManager) {
if (sdkChecker.deviceIsOreoOrAbove()) { // This sdkChecker will be mocked
// If you target Android 8.0 (API level 26) you need a channel
notificationManager.createNotificationChannel()
}
}
单元测试示例
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.verify
import com.nhaarman.mockito_kotlin.verifyZeroInteractions
import com.nhaarman.mockito_kotlin.whenever
@Test
fun createNotificationChannelOnOreoOrAbove() {
whenever(mockSdkChecker.deviceIsOreoOrAbove()).thenReturn(true)
testedClass.createNotificationChannel(mockNotificationManager)
verify(mockNotificationManager).createNotificationChannel()
}
@Test
fun createNotificationChannelBelowOreo() {
whenever(mockSdkChecker.deviceIsOreoOrAbove()).thenReturn(false)
testedClass.createNotificationChannel(mockNotificationManager)
verifyZeroInteractions(mockNotificationManager)
}
解决方案与 java 17 , 2023 。
简单步骤:
1。添加通用方法
private fun setStaticFieldViaReflection(field: Field, value: Any) {
field.isAccessible = true
getModifiersField().also {
it.isAccessible = true
it.set(field, field.modifiers and Modifier.FINAL.inv())
}
field.set(null, value)
}
private fun getModifiersField(): Field {
return try {
Field::class.java.getDeclaredField("modifiers")
} catch (e: NoSuchFieldException) {
try {
val getDeclaredFields0: Method =
Class::class.java.getDeclaredMethod(
"getDeclaredFields0",
Boolean::class.javaPrimitiveType
)
getDeclaredFields0.isAccessible = true
val fields = getDeclaredFields0.invoke(Field::class.java, false) as Array<Field>
for (field in fields) {
if ("modifiers" == field.name) {
return field
}
}
} catch (ex: ReflectiveOperationException) {
e.addSuppressed(ex)
}
throw e
}
}
2。将这些标志添加到gradle(它可以在其他情况下更改)
android {
...
testOptions {
unitTests.all {
jvmArgs(
"--add-opens", "java.base/java.lang=ALL-UNNAMED",
"--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED"
)
}
}
}
3。使用
的例子
private val testSocManufacturer = "test-soc-manufacturer"
setStaticFieldViaReflection(
Build::class.java.getDeclaredField("SOC_MANUFACTURER"),
testSocManufacturer)
private val testSocModel = "test-soc-model"
setStaticFieldViaReflection(
Build::class.java.getDeclaredField("SOC_MODEL"),
testSocModel)
setStaticFieldViaReflection(
Build.VERSION::class.java.getDeclaredField("SDK_INT"),
30)