fbpx

Mocking and stubbing suspend functions with MockK

MockK over the years became my go-to library for mocking suspend functions. Its intuitive syntax and built-in coroutine support simplify testing asynchronous operations. With MockK, you can easily replicate complex behaviors of coroutine-based functions, ensuring tests are reflective of real-world scenarios.

Mock – fake version of an object used in testing to check how other parts of a program interact with it.

Stub – fake part of the program that always returns the same result.

This article will show you how to use both in testing Kotlin code with MockK.

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.

Want to dive deeper?

  • Writing Unit Tests for Coroutines
  • Introduction to Kotest and MockK
  • Testing Coroutines with Kotest and MockK
  • Testing StateFlow for Android

    You get…

  • Instant unlimited access to masterclass recording
  • Instant access to repository and supporting materials
  • You’ll be first to know about upcoming workshops and masterclasses

more insights

Uncategorized
Jarosław Michalik

#kotlinDevChallenge 2023 summary

The KotlinDevChallenge was not just a competition, but a journey of learning, growth, and community engagement. Every participant in this challenge is a winner in

Read More »
KotlinDevChallenge2023
Jarosław Michalik

#kotlinDevChallenge – 24

What do you love most about Kotlin? Productivity increase? Idioms? Or that it isn’t Java? Maybe something else! Share it. It can be short. It

Read More »

AndroidPro newsletter 🚀

join 3057 developers reading AndroidPro newsletter every week

👉 tips & tricks, articles, videos

👉 every Thursday in your inbox

🎁 bonus on signup – Clean Architecture blueprint

brought to you by Jarek Michalik, Kotlin GDE

You can unsubscribe any time. For details visit our privacy policy.

android pro newsletter tips and tricks how to learn android