40

Mockk allows mocking static functions, but how does one mock a Kotlin top level function?

For example, if I have a Kotlin file called HelloWorld.kt, how do I mock the sayHello() function?


HelloWorld.kt

fun sayHello() = "Hello Kotlin!"
Niel de Wet
  • 7,806
  • 9
  • 63
  • 100

5 Answers5

54

The following syntax has worked to me.

mockkStatic(::sayHello.javaMethod!!.declaringClass.kotlin)

I'm surprised there is nothing on the jvm-stdlib yet for this.

Edit: This overload has now been introduced officially: https://github.com/mockk/mockk/pull/518

mockkStatic(::sayHello)
gmazzo
  • 1,135
  • 13
  • 15
  • 8
    This is definitely the best answer because it doesn't rely on hardcoded package path strings that can break if not refactored properly. This was the easiest approach that guarantees you're mocking the right method at compile time. – bboyairwreck Dec 04 '20 at 19:48
  • What about generic functions? this doesn't work. – Haytham Anmar Aug 17 '22 at 12:02
34

There is way to mockk a top level function:

mockkStatic("pkg.FileKt")
every { fun() } returns 5

You just need to know which file this function goes. Check in JAR or stack trace.

oleksiyp
  • 2,659
  • 19
  • 15
  • My question was too simplistic. Could the problem be that the top level function I'm trying to mock has a varargs parameter, but I'm doing `every { sayHello(any(), any(), any()) } returns "Hello Mockk!"` ? – Niel de Wet Oct 19 '18 at 16:03
  • My bad, there was something else that made _that_ fail. – Niel de Wet Oct 19 '18 at 20:25
  • Fill free to submit your problem to Gitter or GitHub issue with some piece of code. Stack Overflow is good for questions like you asked originally, but not for specific problems. – oleksiyp Oct 19 '18 at 21:23
  • @oleksiyp is there something special I need to add to class file because I'm getting "Unresolved reference" I tried building the project but I am not getting anything. I have the same structure as Niel. When I decompile the name is HelloWorldKt but I cannot find it in my tests – svkaka Apr 16 '19 at 13:43
  • 1
    Should be nothing special. Just in case you didn't see it. https://blog.kotlin-academy.com/mocking-is-not-rocket-science-mockk-advanced-features-42277e5983b5 – oleksiyp Apr 16 '19 at 14:13
  • @oleksiyp thanks for quick response, my problem was I didn't wrap it in `"..."` – svkaka Apr 16 '19 at 14:17
  • @oleksiyp, **How may I find the compiled Jar file with Android Studio?** I'm working to mock a top-level variable and need to find how Java compiles the Kotlin file into a class [as described](https://blog.kotlin-academy.com/mocking-is-not-rocket-science-mockk-advanced-features-42277e5983b5#6cb5) in your Medium series. – AdamHurwitz Sep 20 '19 at 18:33
6

To add on previous answers this is working:

mockkStatic("pkg.FileKt")
every { fun() } returns 5

Where mockStatic takes as an argument "package_name:class_file_name" But to simplify the mockStatick call you can give your file a name for the compiler with @file:JvmName directly in the file.

HelloWorld.kt

@file:JvmName("hello")
fun sayHello() = "Hello Kotlin!"

HelloWorldTest.kt

mockkStatic("pkg.hello")
every { fun() } returns 5

More detailed explication on why this is necessary and other examples here:https://blog.kotlin-academy.com/mocking-is-not-rocket-science-mockk-advanced-features-42277e5983b5

vlecoq-v
  • 101
  • 1
  • 4
1

Building on @Sergey's answer:

You could have the actual implementation of the sayHello() function in a variable that's then the default value of a function parameter to sayHello().

This example works:

package tests

import io.mockk.every
import io.mockk.mockk
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test

val sayHelloKotlin = { "Hello Kotlin!" }
fun sayHello(producer: () -> String = sayHelloKotlin): String = producer()

class Tests {
    interface Producer {
        fun produce(): String
    }

    @Test
    fun `Top level mocking`() {
        val mock = mockk<Producer>()
        every { mock.produce() } returns "Hello Mockk"

        val actual = sayHello(mock::produce)
        Assertions.assertEquals(actual, "Hello Mockk")
    }
}

The problem with this is that you're changing production code just to cater for testing, and it feels contrived.

Niel de Wet
  • 7,806
  • 9
  • 63
  • 100
  • Right. You can try the second approach I've described. It allows you not to change production code. – Sergio Oct 19 '18 at 13:11
0

This code doesn't work for me with mockk version 1.10.0 but works well in 1.11.0 (of course need to change mockkStatic(::bar) )

Utils.kt

@file:JvmName("UtilsKt")
package com.example.myapplication

fun foo(): Boolean {
  return bar()
}

fun bar():Boolean {
  return false
}

Test

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.O_MR1])
class ExampleUnitTest {
    @Test
    fun addition_isCorrect() {
        mockkStatic("com.example.myapplication.UtilsKt")
        every { bar() } returns true
        assertTrue(foo())
    }
}
w201
  • 2,018
  • 1
  • 11
  • 15