Kotlin Multiplatform:如何在 iOS 的单元测试中模拟对象

Posted

技术标签:

【中文标题】Kotlin Multiplatform:如何在 iOS 的单元测试中模拟对象【英文标题】:Kotlin Multiplatform: How to mock objects in a unit test for iOS 【发布时间】:2020-02-07 18:08:37 【问题描述】:

我正在开发适用于 ios / android 的 Kotlin 多平台 (KMP) 库。我已经为 JVM 编写了一些单元测试,为此我使用 MockK 来创建间谍和模拟,但 MockK 还不完全支持 Kotlin 本机。

因此,我想知道从事 KMP 项目的其他人如何为 iOS 平台编写单元测试。一个例子将不胜感激。

【问题讨论】:

【参考方案1】:

目前,MockK 不支持 Kotlin/Native 或 Kotlin/JS。但是,两者都被列为重要项目on the project backlog:

Kotlin/Native Kotlin/JS

这是一个巨大的挑战,因为模拟库严重依赖诸如反射之类的语言功能,而这些功能在 Kotlin 多平台上尚未完全支持(如果没有针对特定平台的大量解决方法,甚至可能无法支持)。

这并不妨碍您编写自定义模拟类并验证您在其中设置的状态,当然,这些测试将能够在所有平台上运行。

【讨论】:

【参考方案2】:

我环顾四周并在Kotlin' General slack 中提问,顺便说一句,这是一个很棒的空间,您可以直接向 Kotlin 开发人员和爱好者提问 Kotlin 语言相关问题,包括多平台问题。

但至于我写这篇文章的时候,我认为你不能用 Mockk 或任何其他模拟库来模拟公共模块。

您可以在针对作为平台之一的本机的通用模块中进行测试的是老式的接口/实现/存根方法。

【讨论】:

【参考方案3】:

您可以使用 Mockative 在 Kotlin/Native 和 Kotlin Multiplatform 中模拟接口,这与使用 MockK 或 Mockito 模拟依赖项的方式不同。

全面披露:我是 Mockative 的作者之一

这是一个例子:

class GitHubServiceTests 
    @Mock val api = mock(classOf<GitHubAPI>())

    val service = GitHubService(api)

    @Test
    fun test() 
        // Given
        val id = "mockative/mockative"
        val mockative = Repository(id = id, name = "Mockative")
        given(api).invocation  fetchRepository(id) 
            .thenReturn(mockative)

        // When
        val repository = service.getRepository(id)

        // Then
        assertEquals(mockative, repository)

        // You can also verify function calls on mocks
        verify(api).invocation  fetchRepository(id) 
            .wasInvoked(exactly = once)
    

目前不支持间谍,但他们在路线图上。

【讨论】:

【参考方案4】:

我在 androidTest 中测试了公共资源。有 Mockk 可用。

我认为这就足够了,因为 Android 使用与通用模块完全相同的代码。

shared
> src
>> androidMain 
>> androidTest < tests here
>> commonMain < code here

【讨论】:

测试不应该在 androidTest 上进行吗? 这种方法的一个缺点是在 androidTest 中,您不会遇到 kotlin/native 内存模型的任何问题;改变冻结的数据,通过线程传输状态等。只有在像这样在 iOS 代码库中测试代码时才会遇到这些问题。【参考方案5】:

一个类似于Mockative 的库我发现它提供了一个很好的API,用于在常见测试中模拟接口和伪造数据类是来自Kodein 的MocKMP。文档中的一个简单用法可以在下面找到:

class MyTest : TestsWithMocks() 
    override fun setUpMocks() = injectMocks(mocker) //(1)

    @Mock lateinit var view: View
    @Fake lateinit var model: Model

    val controller by withMocks  Controller(view = view, firstModel = model) 

    @Test fun controllerTest() 
        every  view.render(isAny())  returns true
        controller.start()
        verify  view.render(model) 
    

这里有一个article 解释如何使用它。这种方法唯一需要注意的是,您需要先生成符号,然后才能访问 injectMocks(mocker) 方法,因为该库使用 kotlin 符号处理来生成提供模拟的 this 方法。

【讨论】:

以上是关于Kotlin Multiplatform:如何在 iOS 的单元测试中模拟对象的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Kotlin-Multiplatform 在 iOS 应用程序的后台线程中运行任务?

如何在 Kotlin Multiplatform(纯 kotlin)中进行延迟

Kotlin Multiplatform:如何在 iOS 的单元测试中模拟对象

Kotlin-Multiplatform 中的 CPointer

如何在 Android 和 JVM 目标之间共享 Java 代码(使用 Kotlin Multiplatform)?

如何在 kotlin Multiplatform 和 Swift 中使用默认接口实现