Kotlin Multiplatform: How to mock objects in a unit test for iOS

Viewed 2291

I'm working on a Kotlin-multiplatform (KMP) library for iOS / Android. I have written some unit tests for JVM, for which I use MockK to create spies and mocks, but MockK doesn't support Kotlin native fully yet.

Therefore, I was wondering how others working on KMP projects write unit tests for the iOS platform. An example would be really appreciated.

5 Answers

Currently, MockK does not support Kotlin/Native or Kotlin/JS. However, both are placed as important items on the project backlog:

This is a big challenge given that a mocking library relies heavily on language features such as reflection which are not fully supported yet on Kotlin multiplatform (and possibly even can't be supported without considerable workarounds for specific platforms).

This does not prevent you from writing custom mock classes and verify states you set therein, of course, of which the tests will be able to run on all platforms.

I looked around and asked in Kotlin' General slack which by the way is a great space where you can ask directly the Kotlin devs and the enthusiasts Kotlin language related questions including multi-platform stuff.

But as for the time I'm writing this, I Don't think you can mock for the common module with Mockk or any other mocking library.

What you can for tests in the common module which is targeted to native as one of the platforms is the old-fashioned interface/impl/stub approach.

You can use Mockative to mock interfaces in Kotlin/Native and Kotlin Multiplatform, not unlike how you'd mock dependencies using MockK or Mockito.

Full disclosure: I am one of the authors of Mockative

Here's an example:

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

Spies aren't currently supported, but they are on the roadmap.

I test the common sources in androidTest. There Mockk is available.

I think it is sufficient because Android uses exactly the same code as the common module.

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

One library similar to Mockative I found that provides a nice API for mocking interfaces and fakes for data classes in common tests is MocKMP from the folks at Kodein. A simple usage from the docs can be found below:

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

Here is an article explaining how to use it as well. The only caveat to this approach is you need to generate the symbols first before you have access to injectMocks(mocker) method since the library uses kotlin symbol processing to generate the this method which provides the mocks.

Related