Prerequisites

Lets suppose that in our system we have dependency on use case with suspend method:

interface ProvideCurrentUser {
    suspend fun execute(): UserDetails
}

data class UserDetails(val id: String, val name: String)

This use case is responsible for resolving user currently logged in to app.

We have also dependency on repository, also with suspend function getUserById:

interface CommentsRepository {
    suspend fun getByUserId(userId: String): List<Comment>
}

data class Comment(val id: String, val content: String)

Which will fetch all comments for specific user.

Our goal is to fetch currently logged in user comments:

class FetchCurrentUserComments(
    private val provideUserDataUseCase: ProvideCurrentUser,
    private val commentsRepository: CommentsRepository
) {
    suspend fun getCurrentUserComments(): List<Comment> {
        val user = provideUserDataUseCase.execute()
        return commentsRepository.getByUserId(user.id)
    }
}
System Under Test implementation

Test case implementation

Now let's write a test case for that class. We will start with preparing our "given"/"arrange" section:

Normally we would write stub with MockK like this:

@Test
fun `it should fetch comments for current user`() {
    val givenUser = UserDetails(
        id = "fake_id_1",
        name = "Jarek"
    )

    val provideCurrentUser: ProvideCurrentUser = mockk()
    every { provideCurrentUser.execute() } returns givenUser
}

But ProvideCurrentUser.execute is suspend function, so with MockK we should use coEvery for defining stub:

@Test
fun `it should fetch comments for current user`() {
    val givenUser = UserDetails(
        id = "fake_id_1",
        name = "Jarek"
    )

    val provideCurrentUser: ProvideCurrentUser = mockk()
    coEvery { provideCurrentUser.execute() } returns givenUser
}
Use coEvery for stubbing suspend methods

Every provideCurrentUser.execute() call will return predefined value: givenUser.

Now we can proceed with defining mock for CommentsRepository. In our test we'd like to verify that getByUser was invoked with proper parameters, so we need to create mock for repository dependency:

@Test
fun `it should fetch comments for current user`() {
    val givenUser = UserDetails(
        id = "fake_id_1",
        name = "Jarek"
    )

    val provideCurrentUser: ProvideCurrentUser = mockk()
    coEvery { provideCurrentUser.execute() } returns givenUser

    val commentsRepository: CommentsRepository = mockk()
    coEvery { commentsRepository.getByUserId(any()) } returns emptyList()
}

In CommentsRepository, method getByUserId is also suspend function, so while stubbing we should also use coEvery.
Nevertheless, in this particular test case we don't use any specific return value, so we can use MockK built-in mechanism for providing default values:

@Test
fun `it should fetch comments for current user`() {
    val givenUser = UserDetails(
        id = "fake_id_1",
        name = "Jarek"
    )

    val provideCurrentUser: ProvideCurrentUser = mockk()
    coEvery { provideCurrentUser.execute() } returns givenUser

    val commentsRepository: CommentsRepository = mockk(relaxed = true)
}
Use mockk(relaxed = true) to create mock with default values


Now lets finish setting up our test doubles and create system under test.
The next step is "when"/"act" section: getCurrentUserComments in System Under Test is also suspend function - to call it in regular Junit5 test we have to provide some coroutine context.

Normally we would use runBlockingTest from kotlinx.coroutines.test package, but since we are ignoring async behavior in this test case, we can use regular runBlocking.

@Test
fun `it should fetch comments for current user`() {
    val givenUser = UserDetails(
        id = "fake_id_1",
        name = "Jarek"
    )

    val provideCurrentUser: ProvideCurrentUser = mockk()
    coEvery { provideCurrentUser.execute() } returns givenUser

    val commentsRepository: CommentsRepository = mockk(relaxed = true)

    val sut = FetchCurrentUserComments(provideCurrentUser, commentsRepository)

    runBlocking {
        sut.getCurrentUserComments()
    }

    coVerify { commentsRepository.getByUserId("fake_id_1") }
}
coVerify - to perform verifications on suspend methods

On the very end of this test case we have assertion - MockK function coVerify will verify if given function on mock was invoked with given parameters.

Summary

To add Mockk to your Gradle project, put this line in dependencies block:

    testImplementation 'io.mockk:mockk:1.10.2'

Mocking and stubbing suspend function is necessary if you want to properly test code that relies on coroutines - if you don't want to write custom fake for each test double needed - you may want to make use of MockK library for mocking coroutines calls, as well as for mocking classes and methods without suspend modifier.