Mock package-level function in Kotlin with PowerMock

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP



Mock package-level function in Kotlin with PowerMock



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?





See stackoverflow.com/questions/47985836 for a similar question (the technique probably works for powermock too).
– yole
Mar 16 at 12:30





How MyClass and Logger are related: inheritance, composition? How do you instantiate Logger class? Is Logger.kt a class or a kotlin object?
– Oleg
Mar 16 at 22:26





@Oleg Logger.Kt is a kotlin object
– jmartinalonso
Mar 19 at 7:57





@jmartinalonso, in that case I woult tyr to mock Kotlin object's method call with PowerMockito by annotating this method as @JvmStatic + PowerMockito.mockStatic approach.
– Oleg
Mar 24 at 22:47


@JvmStatic


PowerMockito.mockStatic





@Oleg trying to annotate a package-level function with @JvmStatic give the following error: Only members in named objects and companion objects of classes can be annotated with @JvmStatic
– jmartinalonso
Apr 10 at 7:15


@JvmStatic


Only members in named objects and companion objects of classes can be annotated with @JvmStatic




2 Answers
2



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:


Logger.kt


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.




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.


Logger


@PrepareForTest





Thanks for your response. So, now the question is how to reference the generated java class as class literal to use it for @PrepareForTest . Do you think is it possible?
– jmartinalonso
Mar 19 at 7:56





To my knowledge currently not, but I wish I'm wrong.
– Philipp Hofmann
Mar 20 at 7:43



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.


@PrepareForTest



The first param is value if type Class<?>: here you can provide an array of classes. E.g:


value


Class<?>


@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:


fullyQualifiedNames


String


@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



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


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:


@PrepareForTest


@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:


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.


TopLevelFunctionClass.kt


@file:JvmName("The name you want for this file")






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

Firebase Auth - with Email and Password - Check user already registered

Dynamically update html content plain JS

How to determine optimal route across keyboard