Mocking and stubbing suspend functions 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:

@Testfun `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:

@Testfun `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:

@Testfun `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:

@Testfun `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.

@Testfun `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.

Leave a Comment