【问题标题】:How to unit test a callback in parameter - Kotlin如何对参数中的回调进行单元测试 - Kotlin
【发布时间】:2021-09-13 03:52:26
【问题描述】:

我有一个客户端类(在 Android 应用程序中用 Kotlin 编写),它实现了一个接口 ReadyCallback(在应用程序的库中用 Java 编写,应用程序依赖于这个库)。在客户端中,我有一个createClient() 方法,它将创建一个带有ReadyCallback 参数的客户端。如果准备好了,我会通过调用classC.otherMethod()来执行其他任务,如果没有准备好,我只是创建客户端而不做其他事情:

在图书馆里:

// Somewhere in this library, I have logic to call `readyCallback.onReady()` when I consider it's "ready"
interface ReadyCallback {
    void onReady()
}

class Manager {
    private final ReadyCallback readyCallback;
    public void onConnected(final boolean isConnected) {
        if (isConnected) {
            readyCallback.onReady();
        }
    }
}

在应用中:

class ClassA internal constructor(private val clientProvider: ClassB, private val classC: ClassC, private val classD: ClassD) : ReadyCallback {
    fun createClient() {
        val client = clientProvider.create(getReadyCallback())
    }

    private fun getReadyCallback() {
        return ReadyCallback { onReady() }
    }

    override fun onReady() {
        logInfo { "It's ready! Now do some stuff by calling classC.otherMethod()" }
        classC.otherMethod()
    }
}

在单元测试中,我想验证当我创建客户端并且它准备好时,classC 的 otherMethod() 将被调用。我尝试执行以下操作,但不正确:

import com.nhaarman.mockitokotlin2.*
import org.junit.*

class ClassATest {
    lateinit var unitUnderTest: ClassA
    lateinit var clientProviderMock: ClassB
    lateinit var classCMock: ClassC
    lateinit var clientMock: ClassD

    @Before
    override fun setup() {
        super.setup()
        clientProviderMock = mock()
        classCMock = mock()
        clientMock = mock()
        unitUnderTest = ClassA(clientProvider = clientProviderMock, classC = classCMock, classD = classDMock)

        whenever(clientProviderMock.create(any()).thenReturn(client)
    }

    @Test
    fun `when create client then call otherMethod`() {
        unitUnderTest.createClient()
        verify(classCMock).otherMethod()
    }
}

错误信息显示:

Wanted but not invoked:
classC.otherMethod();
Actually, there were zero interactions with this mock.

我认为我得到这个错误的原因是,如果我不调用getReadyCallback(),这意味着我没有调用回调,所以没有调用classC.otherMethod()。但除此之外,我真的坚持这一点,我不知道如何对我的期望行为进行单元测试(如果准备好,将调用 classC.otherMethod(),如果未准备好,则不会调用此方法)。

我知道我不能执行以下操作,因为 unitUnderTest 不是模拟对象:

callbackMock = mock()
whenever(unitUnderTest.getReadyCallback()).thenReturn(callbackMock)
whenever(clientProviderMock.create(callbackMock).thenReturn(client)

谁能帮帮我?

【问题讨论】:

  • 您好。您能否更新您的问题以包含日志输出以及 Gradle/Maven 文件中的相关依赖项(Kotlin、Mockito、Junit 等),包括版本。检查版本是否对齐很重要。您提供的代码也有错误(缺少括号,void onReady() 不是 Kotlin,缺少 B/C/D 类)所以很难检查 - 您可以更新它以确保它是 reproducible?跨度>
  • 在测试设置中你存根clientProvider.create(...),所以在ClassA.createClient()它永远不会调用getReadyCallback()——取而代之的是Mockito拦截clientProvider.create(...)并立即返回模拟客户端。
  • 对不起,我没有说清楚。是的,它是 Java 而不是 Kotlin,它来自 classA 的包所依赖的另一个包。我试图编辑问题。

标签: java unit-testing kotlin callback mockito-kotlin


【解决方案1】:

我能想到的唯一方法是在回调的onReady() 方法中添加一个布尔标志。所以会变成:

在图书馆:

interface ReadyCallback {
    void onReady(final boolean isReady)
}

class Manager {
    private final ReadyCallback readyCallback;
    public void onConnected(final boolean isConnected) {
        if (isConnected) {
            readyCallback.onReady(true);
        } else {
            readyCallback.onReady(false);
        }
    }
}

应用内:

class ClassA internal constructor(private val clientProvider: ClassB, private val classC: ClassC, private val classD: ClassD) : ReadyCallback {
    fun createClient() {
        val client = clientProvider.create(getReadyCallback())
    }

    private fun getReadyCallback() {
        return ReadyCallback { isReady -> onReady(isReady) }
    }

    override fun onReady(isReady: Boolean) {
        if (isReady) {
            logInfo { "It's ready! Now do some stuff by calling classC.otherMethod()" }
            classC.otherMethod()
        }
    }
}

在单元测试中:

import com.nhaarman.mockitokotlin2.*
import org.junit.*

class ClassATest {
    lateinit var unitUnderTest: ClassA
    lateinit var clientProviderMock: ClassB
    lateinit var classCMock: ClassC
    lateinit var clientMock: ClassD

    @Before
    override fun setup() {
        super.setup()
        clientProviderMock = mock()
        classCMock = mock()
        clientMock = mock()
        unitUnderTest = ClassA(clientProvider = clientProviderMock, classC = classCMock, classD = classDMock)

        whenever(clientProviderMock.create(any()).thenReturn(client)
    }

    @Test
    fun `when create client and ready then call otherMethod`() {
        unitUnderTest.onReady(true)
        unitUnderTest.createClient()
        verify(classCMock).otherMethod()
    }

    @Test
    fun `when create client and not ready then do not call otherMethod`() {
        unitUnderTest.onReady(false)
        unitUnderTest.createClient()
        verifyZeroInteractions(classCMock)
    }
}

但是我仍然不知道如何在没有回调方法中的布尔参数的情况下进行测试。有人知道怎么做吗?

【讨论】:

  • otherMethod() 永远不会被调用(除非你直接调用 onReady()),因为它被包裹在一个 lambda ReadyCallback 中,并且 lambda 被忽略并且永远不会执行,因为 clientProvider 是一个模拟.尝试在没有模拟的情况下编写测试并通过代码调试步骤 - 希望这样可以清楚地说明。
【解决方案2】:

我想我明白了。我不需要onReady() 中的参数。 在图书馆:

interface ReadyCallback {
    void onReady()
}

// place to determine when is "ready"
class Manager {
    private final ReadyCallback readyCallback;
    public void onConnected(final boolean isConnected) {
        if (isConnected) {
            readyCallback.onReady();
        }
    }
}

应用内:

class ClassA internal constructor(private val clientProvider: ClassB, private val classC: ClassC, private val classD: ClassD) : ReadyCallback {
    fun createClient() {
        val client = clientProvider.create(getReadyCallback())
    }

    private fun getReadyCallback() {
        return ReadyCallback { onReady() }
    }

    override fun onReady() {
        logInfo { "It's ready! Now do some stuff by calling classC.otherMethod()" }
        classC.otherMethod()
    }
}

在单元测试中:

import com.nhaarman.mockitokotlin2.*
import org.junit.*

class ClassATest {
    lateinit var unitUnderTest: ClassA
    lateinit var clientProviderMock: ClassB
    lateinit var classCMock: ClassC
    lateinit var clientMock: ClassD

    @Before
    override fun setup() {
        super.setup()
        clientProviderMock = mock()
        classCMock = mock()
        clientMock = mock()
        unitUnderTest = ClassA(clientProvider = clientProviderMock, classC = classCMock, classD = classDMock)

        whenever(clientProviderMock.create(any()).thenReturn(client)
    }

    @Test
    fun `when create client and ready then call otherMethod`() {
        unitUnderTest.onReady()
        unitUnderTest.createClient()
        verify(classCMock).otherMethod()
    }

    @Test
    fun `when create client and not ready then do not call otherMethod`() {
        unitUnderTest.createClient()
        verifyZeroInteractions(classCMock)
    }
}

【讨论】:

  • 如果您解决了问题,请随时将此标记为最佳答案。
  • 看起来这并没有真正测试你的代码。很难说,因为您提供的代码无法编译,但如果您将 return ReadyCallback { onReady() } 更改为 return ReadyCallback { },那么您的测试仍然会通过。它也看起来像 unitUnderTest.createClient() 在测试 when create client and ready then call otherMethod 没有做任何事情,可以被删除。您需要用实际实例替换模拟 - 因为您是存根方法,因此您的代码在测试期间没有执行。
猜你喜欢
  • 2016-09-12
  • 1970-01-01
  • 1970-01-01
  • 2016-09-10
  • 1970-01-01
  • 2019-08-22
  • 1970-01-01
  • 2011-08-22
  • 1970-01-01
相关资源
最近更新 更多