Cannot assign value to LiveData variable while using launch() scope but can assign value to LiveData using runBlocking()

I have a LiveData variable that I want to assign a value to after receiving a response from a HTTP request. I assign this value inside the launch {} scope. However, when I test the code to see that the value has actually been assigned, the test fails stating that the value is null.

I have tried another option which is to use runBlocking {} scope instead of launch {} scope and my test case passes. The library used for networking is Retrofit

Below is the code that uses launch() and makes the test case fail.

private var _state = MutableLiveData<State>()
val state: LiveData<State>
  get() = _state 
 
private var job = Job()

private val uiScope = CoroutineScope(job + Dispatchers.Main)

init {
    executeRequest(....)
}

// Test cases fails with this function
private fun executeRequest(params) {
  uiScope.launch {
    val deferred = repository.getApi()
        .requestAsync(params)

    try {
        val response = deferred.await()

        _state.value = StateSuccess(response)
    } catch (e: Exception) {
        _state.value = StateError("Failure: $e.message")
    }
  }
}

Below is the code that uses runBlocking() and makes the test case pass.

private var _state = MutableLiveData<State>()
val state: LiveData<State>
  get() = _state 
 
private var job = Job()

private val uiScope = CoroutineScope(job + Dispatchers.Main)

init {
    executeRequest(....)
}

// Test cases passes with this function
private fun executeRequest(params) {
  runBlocking {
    val deferred = repository.getApi()
        .requestAsync(params)

    try {
        val response = deferred.await()

        _state.value = StateSuccess(response)
    } catch (e: Exception) {
        _state.value = StateError("Failure: $e.message")
    }
  }
}

Why is the launch() not assigning the value to the LiveData variable?

I have also tried making executeRequest a suspend function like below

private suspend  fun executeRequest(params){
}

and also this

private fun executeRequest(params) {
  uiScope.launch {
    withContext(Dispatcher.IO){
 val deferred = repository.getApi()
        .requestAsync(params)
    try {
        val response = deferred.await()
        _state.postValue(RESPONSE)
    } catch (e: Exception) {
        _state.postValue(StateError("Failure: $e.message"))
    }
    }
  }
}

########

This is the test 

val viewModel = MyViewModel(fakeClientRepository)

val observer = Observer<State> {}

try {
    viewModel.state.observeForever(observer)

    assert(viewModel.state.value == StateSuccess("success"))
} finally {
    viewModel.state.removeObserver(observer)
}
```

Your code is fine, it’s your test that’s broken. The assert is running before the HTTP request even starts. You have to write your test so that it waits for the coroutine you created with launch to complete.

Check out kotlinx-coroutines-test for information on testing coroutines.