Home > Net >  How to mock function without class in Kotlin?
How to mock function without class in Kotlin?

Time:02-28

fun add() {
    return 4 1;
}


class Calculator {
    fun MathUtils() {
        // do something
        // calls add() function
        val x: Int = add()

        // return something
        return x   22
    }
}


class CalculatorTest {
    var c = Calculator()
    
    @Test
    fun MathUtilsSuccess() {
    Assertions.assertThat(
            c.MathUtils()
        ).isEqualTo(24)
    }
}

I am new to unit Testing, I want to know is there any way I can call MathUtils() function (inside Calculator class)

in MathUtilsSuccess() (inside CalculatorTest class) and i have to mock add() function which is not inside any class such that add() always returns 2, so that in success scenario my Test pass.

All the classes are in separate file and fun add() is also in separate file.

P.S : I have broken down my doubt into this simple example, This is not the actual problem i am working on.

CodePudding user response:

Indeed io.mockk:mockk supports to mock top-level functions in Kotlin.

Similar to extension functions, for top-level functions, Kotlin creates a class containing static functions under the hood as well, which in turn can be mocked.

However, you need to know the name of the underlying class created, which hints that probably this approach should only be used very rarely and with caution. I'll first demonstrate a working sample and name some alternative approaches afterwards.


Let's look at an example on how to mock a top-level function with MockK.

foo.kt

package tld.domain.example

fun foo(x: Int): Int = x   3

bar.kt

package tld.domain.example

fun bar(z: Int): Int = foo(z)   2

You can mock static "things", such as extension functions, top-level functions and objects using mockkStatic.

internal class BarKtTest {

    @Test
    internal fun `can work without mock`() {
        unmockkAll() // just to show nothing is mocked anymore

        val result = bar(1)

        assertThat(result, equalTo(6))
    }

    @Test
    internal fun `can be mocked`() {
        mockkStatic("tld.domain.example.FooKt")
        every { foo(any()) } returns 1

        val result = bar(1)

        assertThat(result, equalTo(3))
    }
}

As seen above, one is able to mock the top-level function yielding different results. The example above use com.natpryce:hamkrest for their assertions, but that should not matter.

When using IntelliJ, you can retrieve the name of the underlying class by using Tools > Kotlin > Show Kotlin Bytecode. In my example above, this yields a

public final class tld/domain/example/FooKt {
    ...

There is an overloaded version of mockkStatic that allows to provide a function reference instead of hard-coding the package name and class name as String. Note however, that this relies on the same methodology under the hood.

mockkStatic(::foo)

Instead of using static mocks, you could also make use of the dependency inversion principle, that is you inject the implementation of foo into bar somehow, e.g. through it's parameters or by wrapping it in a class containing a fields or using a higher level function.

fun barWithParam(foo: (Int) -> Int, z: Int): Int =
    foo(z)   2

class BarProvider(private val foo: (Int) -> Int) {
    fun bar(z: Int): Int = foo(z)   2
}

fun barFactory(foo: (Int) -> Int): (Int) -> Int {
    return { z -> foo(z)   2 }
}
val bar = barFactory(::foo)

Another approach is to simply ignore the fact, that bar uses foo under the hood and tests the behaviour of bar without mocking foo. This mostly works, when foo is a pure function that does not have any side-effect, such as making any I/O operations, e.g. network, disk ...

  • Related