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

Ayu*_*ani 6 java mockito kotlin junit5 mockk

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

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

MathUtilsSuccess()(CalculatorTest类内)中,我必须模拟add()不在任何类内的函数,以便add()始终返回2,以便在成功的情况下我的测试通过。

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

PS:我已将我的疑问分解为这个简单的示例,这不是我正在解决的实际问题。

End*_*eit 7

事实上io.mockk:mockk支持模拟 Kotlin 中的顶级函数。

与扩展函数类似,对于顶级函数,Kotlin 也在底层创建了一个包含静态函数的类,而该类又可以被模拟。

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


让我们看一个有关如何使用 模拟顶级函数的示例MockK

foo.kt

package tld.domain.example

fun foo(x: Int): Int = x + 3
Run Code Online (Sandbox Code Playgroud)

巴克特

package tld.domain.example

fun bar(z: Int): Int = foo(z) + 2
Run Code Online (Sandbox Code Playgroud)

您可以使用 来模拟静态“事物”,例如扩展函数顶级函数对象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))
    }
}
Run Code Online (Sandbox Code Playgroud)

如上所示,我们可以模拟顶级函数,从而产生不同的结果。上面的示例使用com.natpryce:hamkrest进行断言,但这并不重要。

使用 IntelliJ 时,您可以使用 检索底层类的名称Tools > Kotlin > Show Kotlin Bytecode。在我上面的例子中,这会产生一个

public final class tld/domain/example/FooKt {
    ...
Run Code Online (Sandbox Code Playgroud)

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

mockkStatic(::foo)
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

另一种方法是简单地忽略这个事实,即在幕后bar使用并测试不嘲笑的行为。当它是一个没有任何副作用的纯函数(例如进行任何 I/O 操作,例如网络、磁盘等)时,这通常有效。foobarfoofoo