I have a file with some package-level functions in Kotlin.
//Logger.kt
fun info(tag : String, message : String){
...
}
fun error{....}
I'm testing functions of a class that call functions of this kotlin file, and I would like to mock them. I know that package-level functions are like static methods in Java, so I've been thinking of using PowerMock.
//MyClass: Class that calls Logger.kt functions
class MyClass {
fun myFunction(){
info("TAG", "Hello world!")
}
}
Any ideas?
There is a workaround you can use to mock top-level function from Kotlin.
Explanation
@PrepareForTest
annotation does have 2 params to provide the context (classes) where you want to mock something or where you mocking stuff in going to be used.
The first param is value
if type Class<?>[]
: here you can provide an array of classes. E.g:
@PrepareForTest(Class1::class, Class2::class, Class3::class, QueryFactory::class)
The second param fullyQualifiedNames
of type String[]
: here you can provide an array with the fully qualified name of the classes. E.g:
@PrepareForTest(Class1::class, fullyQualifiedNames = arrayOf("x.y.z.ClassName"))
Let's say that we have a Kotlin file named "MyUtils.kt" which contains only top-level functions. As you know you cannot reference the MyUtilsKt class from Kotlin files, but from Java you can. It means that there is static class generated (I do not have enough knowledge yet to provide you with more details about this) and it has a fully qualified name.
Solution
This solution is not perfect. I implemented it on our code-base and it seems to work. For sure it can be improved.
TopLevelFunctionClass.kt
where I added the fully qualified names of the "classes" which contain only top-level functions.internal const val MyUtilsKt = "com.x.y.z.MyUtilsKt"
Unfortunately, I had to hardcode the name since an annotation argument must be a compile-time constant.
I updated the @PrepareForTest
annotation of my test class as following:
@RunWith(PowerMockRunner::class)
@PrepareForTest(Class1::class, Class2::class, Class4::class,
fullyQualifiedNames = [MyUtilsKt]) // the string constant declared in TopLevelFunctionClass.kt
I updated the test method as following:
Top-level function in MyUtils.kt
:
internal fun testMock(): Int {
return 4
}
The test method:
@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.
}
Side effect: In case you rename the kotlin file which contains the top-level functions you have to change also the constant defined in TopLevelFunctionClass.kt
. A possible solution to avoid the renaming problem is to add: @file:JvmName("The name you want for this file")
. In case you'll have 2 files with the same name you'll get a duplication JVM class name error.
You can use PowerMock for this. As you already pointed out, Kotlin generates a static Java class for your top level functions in the file Logger.kt
, named LoggerKt.java
. You can change it by annotating the file with @file:JvmName(“...“)
, if you like. Therefore you can do it like this:
@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.
}
}
I tried to make it work in Kotlin, but I didn't find a way to make the from Kotlin generated Logger
Java class available as a class literal to be able to use it for the @PrepareForTest
annotation. Although it is possible to reference the generated Java class in Kotlin.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With