简体   繁体   中英

How to test suspend function using MockK?

I am writing a unit test for my Datarepository layer which simply calls an interface. I am using Kotlin, coroutines and MockK for unit testing. In MockK, how can I verify that I have called apiServiceInterface.getDataFromApi() and has happened only once? Should I put the code in runBlocking?

This is my code:

UnitTest

import com.example.breakingbad.api.ApiServiceInterface
import com.example.breakingbad.data.DataRepository
import io.mockk.impl.annotations.InjectMockKs
import io.mockk.impl.annotations.MockK
import io.mockk.verify
import org.junit.Test

Repository

class DataRepositoryTest {
    @MockK
    private lateinit var apiServiceInterface: ApiServiceInterface

    @InjectMockKs
    private lateinit var dataRepository: DataRepository

    @Test
    fun getCharacters() {
            val respose = dataRepository.getCharacters()
            verify { apiServiceInterface.getDataFromApi() }
    }
}

    class DataRepository @Inject constructor(
    private val apiServiceInterface: ApiServiceInterface
) {
    suspend fun getCharacters(): Result<ArrayList<Character>> = kotlin.runCatching{
        apiServiceInterface.getDataFromApi()
    }
}

Interface

interface ApiServiceInterface {
    @GET("api/characters")
    suspend fun getDataFromApi(): ArrayList<Character>
}

Yes, you should put the dataRepository.getCharacters() call in a runBlocking .

And the verify should be replaced for coVerify .

In the end, the test should look like this:

@Test
fun getCharacters() {
    val respose = runBlocking { dataRepository.getCharacters() }
    
    coVerify { apiServiceInterface.getDataFromApi() }
}

Also, since you want to verify it has happened only once, you need to call coVerify with the exactly parameter coVerify(exactly = 1)

I think you should prefer using runTest instead of runBlocking or runBlockingTest .To tell you in brief about the three.

  1. runBlocking allows you to call suspend functions by blocking a new coroutine and it blocks the current thread until it is completed.

  2. runBlockingTest will immediately execute the suspending function skipping past any delay and enter coroutine block immediately unlike runBlocking which will wait for the amount of the delay

Since kotlinx.coroutines 1.6.0 release, runBlockingTest is deprecated in favour of runTest due to these reasons listed in the migration guide .

  1. runTest() will automatically skip calls to delay() and handle uncaught exceptions. Unlike runBlockingTest(), it will wait for asynchronous callbacks to handle situations where some code runs in dispatchers that are not integrated with the test module.

I hope that answers your question of what to choose among these 3 to test your suspending function. You code would look like this -:

@Test
fun getCharacters() = runTest {
    val response = dataRepository.getCharacters()
    
    coVerify { apiServiceInterface.getDataFromApi() }
}

Also note that as David mentioned above, because getDataFromApi() is asynchronous/suspending function as well, you will have to use coVerify instead of verify to mock the same.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM