I am facing a weird issue while unit testing Coroutines. There are two tests on the class, when run individually, they both pass and when I run the complete test class, one fails with assertion error.
I am using MainCoroutineRule to use the TestCoroutineScope and relying on the latest Coroutine Testing Library
Here is the test :
#Test
fun testHomeIsLoadedWithShowsAndFavorites() {
runBlocking {
// Stubbing network and repository calls
whenever(tvMazeApi.getCurrentSchedule("US", currentDate))
.thenReturn(getFakeEpisodeList())
whenever(favoriteShowsRepository.allFavoriteShowIds())
.thenReturn(arrayListOf(1, 2))
}
mainCoroutineRule.runBlockingTest {
// call home viewmodel
homeViewModel.onScreenCreated()
// Check if loader is shown
assertThat(LiveDataTestUtil.getValue(homeViewModel.getHomeViewState())).isEqualTo(Loading)
// Observe on home view state live data
val homeViewState = LiveDataTestUtil.getValue(homeViewModel.getHomeViewState())
// Check for success data
assertThat(homeViewState is Success).isTrue()
val homeViewData = (homeViewState as Success).homeViewData
assertThat(homeViewData.episodes).isNotEmpty()
// compare the response with fake list
assertThat(homeViewData.episodes).hasSize(getFakeEpisodeList().size)
// compare the data and also order
assertThat(homeViewData.episodes).containsExactlyElementsIn(getFakeEpisodeViewDataList(true)).inOrder()
}
}
The other test is almost similar which tests for Shows without favorites. I am trying to test HomeViewModel method as:
homeViewStateLiveData.value = Loading
val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception ->
onError(exception)
}
viewModelScope.launch(coroutineExceptionHandler) {
// Get shows from network and favorites from room db on background thread
val favoriteShowsWithFavorites = withContext(Dispatchers.IO) {
val favoriteShowIds = favoriteShowsRepository.allFavoriteShowIds()
val episodes = tvMazeApi.getCurrentSchedule(COUNTRY_US, currentDate)
getShowsWithFavorites(episodes, favoriteShowIds)
}
// Return the combined result on main thread
withContext(Dispatchers.Main) {
onSuccess(favoriteShowsWithFavorites)
}
}
}
I cannot find the actual cause of why the tests if run separately are passing and when the complete class is tested, one of them is failing. Pls help if I am missing something
Retrofit and Room that come with Coroutine support owner the suspend functions and move them off the UI thread by their own. Thus, they reduce the hassles of handling thread callbacks by the developers in a big way. Initially, I was moving the suspend calls of network and DB to IO via Dispatchers.IO explicitly. This was unnecessary and also leading unwanted context-switching leading to flaky test. Since the libraries, automatically do it, it was just about handling the data back on UI when available.
viewModelScope.launch(coroutineExceptionHandler) {
// Get favorite shows from db, suspend function in room will launch a new coroutine with IO dispatcher
val favoriteShowIds = favoriteShowsRepository.allFavoriteShowIds()
// Get shows from network, suspend function in retrofit will launch a new coroutine with IO dispatcher
val episodes = tvMazeApi.getCurrentSchedule(COUNTRY_US, currentDate)
// Return the result on main thread via Dispatchers.Main
homeViewStateLiveData.value = Success(HomeViewData(getShowsWithFavorites(episodes, favoriteShowIds)))
}
Related
I have an unit test like this:
...
subj.mintToken(to, value, uri)
advanceUntilIdle()
...
val pendingTxFinalState = subj.uiState.value.pendingTx.count()
assertThat("Model should have a single pending tx, but has $pendingTxFinalState", pendingTxFinalState == 1)
...
The model field in ViewModel is populated by the request to cache in the init {} block. Each change in table would trigger this coroutine flow. This piece of unit test checks correctness of this functionality.
The current issue is this Flow in init {} block is triggered only on the test start when ViewModel instance is created. It does not respond on update in table.
It is important to note I don't use in test a room database neither test database, but FakeCacheRepository where behaviour of methods are emulated by flow with mocked data. However the behaviour of flow should be the same as there is still in change in underlying data.
val txPool = ConcurrentLinkedQueue<ITransaction>()
override fun createChainTx(tx: ITransaction): Flow<ITransaction> {
return flow {
txPool.add(tx)
emit(tx)
}
}
override fun getAllChainTransactions(): Flow<List<ITransaction>> {
return flow {
emit(txPool.toList())
}
}
Do you see the issue here or better way to test this?
My guess is you’re writing you’re own FakeCacheRepo and in the update function you are calling createChainTx. The value of the flow isn’t updating though because the create function doesn’t just update the value it creates a new flow instead of updating the old one. You can modify the set up to emit continuously in a loop (with some buffer delay) based on a variable. Then when you change the variable it will change what the current flow is emiting as expected.
The code example here is roughly doing that: https://developer.android.com/kotlin/flow#create
override fun createChainTx(): Flow<ITransaction> {
return flow {
while(true) {
val tx = getLatestTxValue() // Get the latest updated value from an outside source
txPool.add(tx)
emit(tx)
delay(refreshIntervalMs) // Suspends the coroutine for some time
}
}
}
I have an instance of CoroutineScope and log() function which look like the following:
private val scope = CoroutineScope(Dispatchers.IO)
fun log(message: String) = scope.launch { // launching a coroutine
println("$message")
TimeUnit.MILLISECONDS.sleep(100) // some blocking operation
}
And I use this test code to launch coroutines:
repeat(5) { item ->
log("Log $item")
}
The log() function can be called from any place, in any Thread, but not from a coroutine.
After a couple of tests I can see not sequential result like the following:
Log 0
Log 2
Log 4
Log 1
Log 3
There can be different order of printed logs. If I understand correctly the execution of coroutines doesn't guarantee to be sequential. What it means is that a coroutine for item 2 can be launched before the coroutine for item 0.
I want that coroutines were launched sequentially for each item and "some blocking operation" would execute sequentially, to always achieve next logs:
Log 0
Log 1
Log 2
Log 3
Log 4
Is there a way to make launching coroutines sequential? Or maybe there are other ways to achieve what I want?
Thanks in advance for any help!
One possible strategy is to use a Channel to join the launched jobs in order. You need to launch the jobs lazily so they don't start until join is called on them. trySend always succeeds when the Channel has unlimited capacity. You need to use trySend so it can be called from outside a coroutine.
private val lazyJobChannel = Channel<Job>(capacity = Channel.UNLIMITED).apply {
scope.launch {
consumeEach { it.join() }
}
}
fun log(message: String) {
lazyJobChannel.trySend(
scope.launch(start = CoroutineStart.LAZY) {
println("$message")
TimeUnit.MILLISECONDS.sleep(100) // some blocking operation
}
)
}
Since Flows are sequential we can use MutableSharedFlow to collect and handle data sequentially:
class Info {
// make sure replay(in case some jobs were emitted before sharedFlow is being collected and could be lost)
// and extraBufferCapacity are large enough to handle all the jobs.
// In case some jobs are lost try to increase either of the values.
private val sharedFlow = MutableSharedFlow<String>(replay = 10, extraBufferCapacity = 10)
private val scope = CoroutineScope(Dispatchers.IO)
init {
sharedFlow.onEach { message ->
println("$message")
TimeUnit.MILLISECONDS.sleep(100) // some blocking or suspend operation
}.launchIn(scope)
}
fun log(message: String) {
sharedFlow.tryEmit(message)
}
}
fun test() {
val info = Info()
repeat(10) { item ->
info.log("Log $item")
}
}
It always prints the logs in the correct order:
Log 0
Log 1
Log 2
...
Log 9
It works for all cases, but need to be sure there are enough elements set to replay and extraBufferCapacity parameters of MutableSharedFlow to handle all items.
Another approach is
Using Dispatchers.IO.limitedParallelism(1) as a context for the CoroutineScope. It makes coroutines run sequentially if they don't contain calls to suspend functions and launched from the same Thread, e.g. Main Thread. So this solution works only with blocking (not suspend) operation inside launch coroutine builder:
private val scope = CoroutineScope(Dispatchers.IO.limitedParallelism(1))
fun log(message: String) = scope.launch { // launching a coroutine from the same Thread, e.g. Main Thread
println("$message")
TimeUnit.MILLISECONDS.sleep(100) // only blocking operation, not `suspend` operation
}
It turns out that the single thread dispatcher is a FIFO executor. So limiting the CoroutineScope execution to one thread solves the problem.
I have still a little bit of trouble putting all information together about the thread-safety of using coroutines to launch network requests.
Let's say we have following use-case, there is a list of users we get and for each of those users, I will do some specific check which has to run over a network request to the API, giving me some information back about this user.
The userCheck happens inside a library, which doesn't expose suspend functions but rather still uses a callback.
Inside of this library, I have seen code like this to launch each of the network requests:
internal suspend fun <T> doNetworkRequest(request: suspend () -> Response<T>): NetworkResult<T> {
return withContext(Dispatchers.IO) {
try {
val response = request.invoke()
...
According to the documentation, Dispatchers.IO can use multiple threads for the execution of the code, also the request function is simply a function from a Retrofit API.
So what I did is to launch the request for each user, and use a single resultHandler object, which will add the results to a list and check if the length of the result list equals the length of the user list, if so, then all userChecks are done and I know that I can do something with the results, which need to be returned all together.
val userList: List<String>? = getUsers()
val userCheckResultList = mutableListOf<UserCheckResult>()
val handler = object : UserCheckResultHandler {
override fun onResult(
userCheckResult: UserCheckResult?
) {
userCheckResult?.let {
userCheckResultList.add(
it
)
}
if (userCheckResultList.size == userList?.size) {
doSomethingWithResultList()
print("SUCCESS")
}
}
}
userList?.forEach {
checkUser(it, handler)
}
My question is: Is this implementation thread-safe? As far as I know, Kotlin objects should be thread safe, but I have gotten feedback that this is possibly not the best implementation :D
But in theory, even if the requests get launched asynchronous and multiple at the same time, only one at a time can access the lock of the thread the result handler is running on and there will be no race condition or problems with adding items to the list and comparing the sizes.
Am I wrong about this?
Is there any way to handle this scenario in a better way?
If you are executing multiple request in parallel - it's not. List is not thread safe. But it's simple fix for that. Create a Mutex object and then just wrap your operation on list in lock, like that:
val lock = Mutex()
val userList: List<String>? = getUsers()
val userCheckResultList = mutableListOf<UserCheckResult>()
val handler = object : UserCheckResultHandler {
override fun onResult(
userCheckResult: UserCheckResult?
) {
lock.withLock {
userCheckResult?.let {
userCheckResultList.add(
it
)
}
if (userCheckResultList.size == userList?.size) {
doSomethingWithResultList()
print("SUCCESS")
}
}
}
}
userList?.forEach {
checkUser(it, handler)
}
I have to add that this whole solution seems very hacky. I would go completely other route. Run all of your requests wrapping those in async { // network request } which will return Deferred object. Add this object to some list. After that wait for all of those deferred objects using awaitAll(). Like that:
val jobs = mutableListOf<Job>()
userList?.forEach {
// i assume checkUser is suspendable here
jobs += async { checkUser(it, handler) }
}
// wait for all requests
jobs.awaitAll()
// After that you can access all results like this:
val resultOfJob0 = jobs[0].getCompleted()
While I was learning coroutines and how to properly use them in an android app I found something I was surprised about.
When launching a coroutine using viewModelScope.launch { } and setting a breakpoint inside the launch lambda I noticed my app wasn't responsive anymore because it was still on the main thread.
This confuses me because the docs of viewModelScope.launch { } clearly state:
Launches a new coroutine without blocking the current thread
Isn't the current thread the main thread ? What is the whole purpose of launch if it doesn't run on a different thread by default ?
I was able to run it on anther thread using viewModelScope.launch(Dispatchers.IO){ } which works as I was expecting, namely on another thread.
What I am trying to accomplish from the launch method is to call a repository and do some IO work namely call a webservice and store the data in a room db. So I was thinking of calling viewModelScope.launch(Dispatchers.IO){ } do all the work on a different thread and in the end update the LiveData result.
viewModelScope.launch(Dispatchers.IO){
liveData.postValue(someRepository.someWork())
}
So my second question is, is this the way to go ?
ViewModelScope.launch { } runs on the main thread, but also gives you the option to run other dispatchers, so you can have UI & Background operations running synchronously.
For you example:
fun thisWillRunOnMainThread() {
viewModelScope.launch {
//below code will run on UI thread.
showLoadingOnUI()
//using withContext() you can run a block of code on different dispatcher
val result = novel.id = withContext(Dispatchers.IO) {
withsomeRepository.someWork()
}
//The below code waits until the above block is executed and the result is set.
liveData.value = result
finishLoadingOnUI()
}
}
For more reference, I would say there are some neat articles that will help you understand this concept.
Medium link that explains it really neat.
So my second question is, is this the way to go ?
I would expect two things to be different in your current approach.
1.) First step would be to define the scheduler of the background operation via withContext.
class SomeRepository {
suspend fun doWork(): SomeResult = withContext(Dispatchers.IO) {
...
}
}
This way, the operation itself runs on a background thread, but you didn't force your original scope to be "off-thread".
2.) Jetpack Lifecycle KTX provides the liveData { coroutine builder so that you don't have to postValue to it manually.
val liveData: LiveData<SomeResult> = liveData {
emit(someRepository.someWork())
}
Which in a ViewModel, you would use like so:
val liveData: LiveData<SomeResult> = liveData(context = viewModelScope.coroutineContext) {
withContext(Dispatchers.IO) {
emit(someRepository.someWork())
}
}
And now you can automatically trigger data-loading via observing, and not having to manually invoke viewModelScope.launch {}.
The idea behind main thread being default is you can run UI operations without having to change the context. It is a convention I guess Kotlin coroutine library writers have chosen
Suppose if by default if the launch runs on IO thread then the code would look like this
viewmodelScope.launch {
val response = networkRequest()
withContext(Dispatchers.Main) {
renderUI(response)
}
}
Suppose if by default if the launch runs on Default thread then the code would look like this
viewmodelScope.launch {
val response: Response = null
withContext(Dispatchers.IO) {
response = networkRequest()
}
withContext(Dispatchers.Main) {
renderUI(response)
}
}
Since the default launch is on main thread, now you have to do below
viewmodelScope.launch {
val response: Response = null
withContext(Dispatchers.IO) {
response = networkRequest()
}
renderUI(response)
}
To avoid the messy code initializing the response with null, we can also make the networkRequest as suspend and wrap the business logic of networkRequest() function in withContext(Dispatchers.IO) and that's how lot of people write their networkRequest() function as well! Hope this makes sense
One of the main reasons it runs on Main thread, is because it's more practical for general use in ViewModel, like murali kurapati wrote. It was a design choice.
It's also important to note that all suspending functions should be "main safe" according to best pracices. That means, that your repository should switch coroutine context accordingly, like so:
class someRepository(private val ioDispatcher: CoroutineDispatcher) {
suspend fun someWork() {
withContext(ioDispatcher) {
TODO("Heavy lifting")
}
}
}
I am testing Kotlin coroutines in my Android app and I am trying to do the following unit test
#Test fun `When getVenues success calls explore venues net controller and forwards result to listener`() =
runBlocking {
val near = "Barcelona"
val result = buildMockVenues()
val producerJob = produce<List<VenueModel>>(coroutineContext) { result.value }
whenever(venuesRepository.getVenues(eq(near))) doReturn producerJob // produce corooutine called inside interactor.getVenues(..)
interactor.getVenues(near, success, error) // call to real method
verify(venuesRepository).getVenues(eq(near))
verify(success).invoke(argThat {
value == result.value
})
}
The interactor method is as follows
fun getVenues(near: String, success: Callback<GetVenuesResult>,
error: Callback<GetVenuesResult>) =
postExecute {
repository.getVenues(near).consumeEach { venues ->
if (venues.isEmpty()) {
error(GetVenuesResult(venues, Throwable("No venues where found")))
} else {
success(GetVenuesResult(venues))
}
}
}
postExecute{..} is a method on a BaseInteractor that executes the function in the ui thread through a custom Executor that uses the launch(UI) coroutine from kotlin android coroutines library
fun <T> postExecute(uiFun: suspend () -> T) =
executor.ui(uiFun)
Then the repository.getVenues(..) function is also a coroutine that returns the ProducerJob using produce(CommonPool) {}
The problem is that it seams that success callback in the interactor function doesn't seem to be executed as per the
verify(success).invoke(argThat {
value == result.value
})
However, I do see while debugging that the execution in the interactor function reaches to the if (venues.isEmpty()) line inside the consumeEach but then from there exits and continues with the test, obviously failing on the verify for the success callback.
I am a bit new on coroutines so any help would be appreciated.
I figured this one out. I saw that the problem was just with this producing coroutine and not with the others tests that are also using coroutines and working just fine. I noticed that I actually missed the send on the mocked ProducingJob in order to have it actually produce a value, in this case the list of mocks. I just added that changing the mock of the producing job to
val producerJob = produce { send(result.value) }