4

鉴于此代码:

// Subject.kt

open class Subject(var x: Int) {

    constructor(): this(42) {
        println("made it")
    }

    fun doit() {
        x += 1
        println("did it: $x")
    }
}
// Tests.kt

import org.junit.jupiter.api.Test
import org.mockito.Mockito

class Tests {
    @Test
    fun makeit() {
        val mock = Mockito.mock(Subject::class.java)

        val details = Mockito.mockingDetails(mock)
        println("Is mock: ${details.isMock}")
        println("Is spy:  ${details.isSpy}")

        mock.doit()
        mock.doit()
    }
}

运行时makeit,输出为:

Is mock: true
Is spy:  false
did it: 1
did it: 2

这似乎表明正在创建主题的某些实例,但绕过了潜在的关键构造函数逻辑。这与“部分模拟”是一致的,但代码并没有做任何事情来请求这样的事情。

我发现这是默认行为令人惊讶,因为文档都强烈警告不要使用部分模拟。我一直无法找到描述何时mock()返回部分模拟的文档,因此无法弄清楚如何从课程中获得“完整模拟”。

所以:

  • 何时Mockito.mock()创建部分模拟?
  • Mockito 可以为班级创建“完整模拟”吗?还是只是为了一个界面?
  • 如何请求“完整模拟”?
4

1 回答 1

2

通过源代码和反复试验,我得出以下结论:

  1. 在模拟一个类时,Mockito 创建一个 ByteBuddy 生成的类的子类的实例,而不调用构造函数=> 所有成员数据都是默认值。
  2. 开放方法(Java 的默认方法;open在 Kotlin 中声明为 with):
    • 默认情况下调用并返回返回类型的默认值。
    • 配置时会调用when(...).thenCallRealMethod().
    • 如果模拟创建时defaultAnswer设置为CALLS_REAL_METHODS
  3. final方法不能被覆盖 => 它们将被正常调用,但它们将看到所有成员数据的默认值。

因此,似乎所有类模拟都是部分模拟,但由于 Java 中的默认设置是开放方法,它们通常看起来像常规模拟。默认情况下,它们实际上是常规模拟。

这在 Kotlin 中很快就出现了,因为方法是final默认的。

知道这是如何工作的,可以让处理类模拟变得不那么令人沮丧!

于 2019-12-12T18:47:56.847 回答