使用PowerMock模拟Kotlin中的包级功能

时间:2018-03-16 09:32:32

标签: unit-testing kotlin mockito powermock

我在Kotlin中有一些包含一些包级函数的文件。

//Logger.kt

fun info(tag : String, message : String){
...
}

fun error{....}

我正在测试调用此kotlin文件函数的类的函数,我想嘲笑它们。我知道包级函数就像Java中的静态方法一样,所以我一直在考虑使用PowerMock。

//MyClass: Class that calls Logger.kt functions
class MyClass {

   fun myFunction(){
       info("TAG", "Hello world!")
   }

}

有什么想法吗?

2 个答案:

答案 0 :(得分:2)

您可以使用PowerMock。正如您已经指出的那样,Kotlin为文件Logger.kt中的顶级函数生成一个静态Java类,名为LoggerKt.java。如果您愿意,可以通过使用@file:JvmName(“...“)注释文件来更改它。因此你可以这样做:

@RunWith(PowerMockRunner.class)
@PrepareForTest(LoggerKt.class)
public class MyClassTest {

    @Test
    public void testDoIt() {
        PowerMockito.mockStatic(LoggerKt.class);

        MyClass sut = new MyClass();
        sut.myFunction(); //the call to info(...) is mocked.
    }
}

我试图让它在Kotlin中运行,但是我没有找到一种方法可以将Kotlin生成的Logger Java类作为类文字提供,以便能够将它用于{{ 1}}注释。虽然在Kotlin中有可能to reference the generated Java class

答案 1 :(得分:1)

有一种解决方法,可以用来模拟Kotlin的顶级功能。

说明

@PrepareForTest注释确实有2个参数来提供上下文(类),您可以在其中模拟对象或在要使用的东西中进行模拟。

如果类型为value,则第一个参数为Class<?>[]:在这里您可以提供一组类。例如:

@PrepareForTest(Class1::class, Class2::class, Class3::class, QueryFactory::class)

类型为fullyQualifiedNames的第二个参数String[]:在这里您可以为数组提供类的完全限定名称。例如:

@PrepareForTest(Class1::class, fullyQualifiedNames = arrayOf("x.y.z.ClassName"))

比方说,我们有一个名为“ MyUtils.kt”的Kotlin文件,其中仅包含顶级功能。如您所知,您不能从Kotlin文件中引用MyUtilsKt类,但是可以从Java中引用。这意味着将生成静态类(我尚不具备足够的知识来为您提供有关此的更多详细信息),并且它具有完全限定的名称。

解决方案

此解决方案并不完美。我在我们的代码库中实现了它,并且似乎可以正常工作。当然可以改进。

  1. 我创建了一个名为TopLevelFunctionClass.kt的Kotlin文件,在其中添加了仅包含顶层函数的“类”的完全限定名称。

internal const val MyUtilsKt = "com.x.y.z.MyUtilsKt"

不幸的是,我必须对名称进行硬编码,因为注释参数必须是编译时常量。

  1. 我更新了测试类的@PrepareForTest批注,如下所示:

    @RunWith(PowerMockRunner::class)
    @PrepareForTest(Class1::class, Class2::class, Class4::class,
    fullyQualifiedNames = [MyUtilsKt]) // the string constant declared in TopLevelFunctionClass.kt
    
  2. 我更新了以下测试方法:

    MyUtils.kt中的顶级功能:

    internal fun testMock(): Int {
        return 4
    }
    

    测试方法:

    @Test
    fun myTestMethod() {
        ...
        mockStatic(Class.forName(MyUtilsKt)) // the string constant declared in TopLevelFunctionClass.kt
        `when`(testMock()).thenReturn(10)
        assertEquals(10, testMock()) // the test will successfully pass.
    }
    

副作用:如果要重命名包含顶级功能的kotlin文件,则还必须更改TopLevelFunctionClass.kt中定义的常数。避免重命名问题的可能解决方案是添加:@file:JvmName("The name you want for this file")。如果您有两个具有相同名称的文件,则会收到重复的JVM类名称错误。