android-如何在Kotlin中模拟对象?

我想测试一个调用对象的类(java中的静态方法调用),但是我无法模拟该对象以避免执行真正的方法.

object Foo {
    fun bar() {
        //Calls third party sdk here
    }
}

我尝试了不同的选项,例如MockkHow to mock a Kotlin singleton object?,并以与Java中相同的方式使用PowerMock,但没有成功.

使用PowerMockito的代码:

@RunWith(PowerMockRunner::class)
@PrepareForTest(IntentGenerator::class)
class EditProfilePresenterTest {

    @Test
    fun shouldCallIntentGenerator() {

        val intent = mock(Intent::class.java)

        PowerMockito.mockStatic(IntentGenerator::class.java)
        PowerMockito.`when`(IntentGenerator.newIntent(any())).thenReturn(intent) //newIntent method param is context

       presenter.onGoToProfile()

       verify(view).startActivity(eq(intent))        

    }
}

有了这段代码,我得到

java.lang.IllegalArgumentException: Parameter specified as non-null is null: method com.sample.test.IntentGenerator$Companion.newIntent, parameter context

any()方法来自mockito_kotlin.然后,如果我将模拟的上下文传递给newIntent方法,则似乎调用了真实方法.

解决方法:

首先,该对象IntentGenerator看起来像代码的味道,为什么要使其成为对象?如果不是您的代码,则可以轻松创建包装器类

class IntentGeneratorWrapper {

    fun newIntent(context: Context) = IntentGenerator.newIntent(context)    

}

并在您的代码中使用该代码,而无需静态依赖项.

话虽如此,我有2个解决方案.说你有一个对象

object IntentGenerator {
    fun newIntent(context: Context) = Intent()
}

解决方案1-Mockk

与Mockito相比,使用Mockk库的语法有点有趣,但是,它可以工作:

testCompile "io.mockk:mockk:1.7.10"
testCompile "com.nhaarman:mockito-kotlin:1.5.0"

然后在测试中,将objectMockk fun与对象作为参数一起使用,这将返回您调用use的作用域,在use body中,您可以模拟对象:

@Test
fun testWithMockk() {
    val intent: Intent = mock()
    whenever(intent.action).thenReturn("meow")

    objectMockk(IntentGenerator).use {
        every { IntentGenerator.newIntent(any()) } returns intent
        Assert.assertEquals("meow", IntentGenerator.newIntent(mock()).action)
    }
}

解决方案2-Mockito反射

在您的测试资源文件夹中,创建一个模拟扩展文件夹(例如,如果您的模块是“ app”-> app / src / test / resources / mockito-extensions),并在其中创建一个名为org.mockito.plugins.MockMaker的文件.在文件中,只写这一行mock-maker-inline.现在您可以模拟最终的类和方法(IntentGenerator类和newIntent方法都是最终的).

那你需要

>创建IntentGenerator的实例.考虑到IntentGenerator只是一个常规的Java类,我邀请您使用Android Studio中的Kotlin字节码窗口进行检查
>在该实例上使用Mockito创建一个间谍对象并模拟该方法
>从INSTANCE字段中删除最终修饰符.当您在Kotlin中声明对象时,会发生这样的情况:使用私有构造函数和静态INSTANCE方法创建了一个类(在本例中为IntentGenerator).也就是说,一个单身人士.
>用您自己的模拟实例替换IntentGenerator.INSTANCE值.

完整的方法如下所示:

@Test
fun testWithReflection() {
    val intent: Intent = mock()
    whenever(intent.action).thenReturn("meow")

    // instantiate IntentGenerator
    val constructor = IntentGenerator::class.java.declaredConstructors[0]
    constructor.isAccessible = true
    val intentGeneratorInstance = constructor.newInstance() as IntentGenerator

    // mock the the method
    val mockedInstance = spy(intentGeneratorInstance)
    doAnswer { intent }.`when`(mockedInstance).newIntent(any())

    // remove the final modifier from INSTANCE field
    val instanceField = IntentGenerator::class.java.getDeclaredField("INSTANCE")
    val modifiersField = Field::class.java.getDeclaredField("modifiers")
    modifiersField.isAccessible = true
    modifiersField.setInt(instanceField, instanceField.modifiers and Modifier.FINAL.inv())

    // set your own mocked IntentGenerator instance to the static INSTANCE field
    instanceField.isAccessible = true
    instanceField.set(null, mockedInstance)

    // and BAM, now IntentGenerator.newIntent() is mocked
    Assert.assertEquals("meow", IntentGenerator.newIntent(mock()).action)
}

问题是,在模拟对象之后,模拟的实例将保留在那里,其他测试可能会受到影响.一个关于如何将模拟限制到范围here的示例

为什么PowerMock无法正常工作

你正在

Parameter specified as non-null is null

因为没有模拟IntentGenerator,所以正在调用的方法newIntent是实际的方法,并且在Kotlin中,具有非null参数的方法将在方法开始时调用kotlin / jvm / internal / Intrinsics.checkParameterIsNotNull.您可以使用Android Studio中的字节码查看器进行检查.如果您将代码更改为

PowerMockito.mockStatic(IntentGenerator::class.java)
PowerMockito.doAnswer { intent }.`when`(IntentGenerator).newIntent(any())

您会收到另一个错误

org.mockito.exceptions.misusing.NotAMockException: Argument passed to
when() is not a mock!

如果对象被嘲笑,则调用的newInstance方法将不是实际类中的那个,因此即使在签名中它不能为空,也可以将null用作参数.

上一篇:Spring Boot项目中使用Mockito


下一篇:java-模拟Spring Boot