Build.VERSION的存根值本地单元测试中的SDK_INT



我想知道是否有任何方法来存根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)

相关内容

  • 没有找到相关文章

最新更新