如何在 Kotlin 中模拟没有 class 的函数?

How to mock function without class in Kotlin?

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)
    }
}

我是单元测试的新手,我想知道有什么方法可以调用 MathUtils() 函数(在计算器 class 内)

in MathUtilsSuccess()(在 CalculatorTest class 内),我必须模拟 add() 函数,它不在任何 class 内,这样 add() 总是 returns 2,这样在成功场景中我的测试就通过了。

所有 classes 都在单独的文件中,fun add() 也在单独的文件中。

P.S : 我已经将我的疑问分解为这个简单的例子,这不是我正在处理的实际问题。

确实 io.mockk:mockk 支持在 Kotlin 中模拟 top-level 函数。

extension functions 类似,对于 top-level 函数,Kotlin 创建了一个 class 并在幕后包含静态函数,这些函数又可以被模拟。

但是,您需要知道所创建的基础 class 的名称,这暗示这种方法可能只应很少使用并谨慎使用。我将首先演示一个工作示例,然后命名一些替代方法。


让我们看一个关于如何使用 MockK.

模拟 top-level 函数的例子

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

您可以模拟静态“事物”,例如 扩展函数 top-level 函数 对象 使用 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))
    }
}

如上所示,可以模拟 top-level 函数产生不同的结果。 上面的示例使用 com.natpryce:hamkrest 作为他们的断言,但这应该无关紧要。

使用 IntelliJ 时,您可以使用 Tools > Kotlin > Show Kotlin Bytecode 检索基础 class 的名称。在我上面的例子中,这会产生 a

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

mockkStatic 有一个重载版本,允许提供函数引用而不是 hard-coding 包名和 class 名称作为字符串。但是请注意,这在幕后依赖于相同的方法。

mockkStatic(::foo)

除了使用静态模拟之外,您还可以利用依赖倒置原则,即将 foo 的实现以某种方式注入 bar,例如通过它的参数或将其包装在包含字段或使用更高级别函数的 class 中。

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)

另一种方法是简单地忽略 bar 在幕后使用 foo 并在不模拟 foo 的情况下测试 bar 的行为这一事实。这主要适用于 foo 是没有任何 side-effect 的纯函数,例如进行任何 I/O 操作,例如网络、磁盘...