I am almost new to android testing and following the official docs and Udacity course for learning purposes.
Coming to the issue I want to check when the task is completed or incompleted to be displayed properly or not, for this I wrote a few tests. Here I got the exception that toast can not be displayed on a thread that has not called Looper.prepare.
When I comment out the toast msg live data updating line of code then all tests work fine and pass successfully. I am new to android testing and searched out a lot but did not get any info to solve this issue. Any help would be much appreciated. A little bit of explanation will be much more helpful if provided.
Below is my test class source code along with ViewModel, FakeRepository, and fragment source code.
Test Class.
@ExperimentalCoroutinesApi
@MediumTest
@RunWith(AndroidJUnit4::class)
class TaskDetailFragmentTest {
    @get:Rule
    var mainCoroutineRule = MainCoroutineRule()
    @get:Rule
    val rule = InstantTaskExecutorRule()
    private lateinit var tasksRepository: FakeTasksRepository
    @Before
    fun setUp() {
        tasksRepository = FakeTasksRepository()
        ServiceLocator.taskRepositories = tasksRepository
    }
    
    @Test
    fun addNewTask_addNewTaskToDatabase() = mainCoroutineRule.runBlockingTest {
        val newTask = Task(id = "1", userId = 0, title = "Hello AndroidX World",false)
        tasksRepository.addTasks(newTask)
        val task = tasksRepository.getTask(newTask.id)
        assertEquals(newTask.id,(task as Result.Success).data.id)
    }
    @Test
    fun activeTaskDetails_DisplayedInUi() = mainCoroutineRule.runBlockingTest {
        val newTask = Task(id = "2", userId = 0, title = "Hello AndroidX World",false)
        tasksRepository.addTasks(newTask)
        val bundle = TaskDetailFragmentArgs(newTask.id).toBundle()
        launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.Theme_ToDoWithTDD)
        onView(withId(R.id.title_text)).check(matches(isDisplayed()))
        onView(withId(R.id.title_text)).check(matches(withText("Hello AndroidX World")))
        onView(withId(R.id.complete_checkbox)).check(matches(isDisplayed()))
        onView(withId(R.id.complete_checkbox)).check(matches(isNotChecked()))
    }
    @Test
    fun completedTaskDetails_DisplayedInUI() = mainCoroutineRule.runBlockingTest {
        val newTask = Task(id = "2", userId = 0, title = "Hello AndroidX World",true)
        tasksRepository.addTasks(newTask)
        val bundle = TaskDetailFragmentArgs(newTask.id).toBundle()
        launchFragmentInContainer <TaskDetailFragment>(bundle,R.style.Theme_ToDoWithTDD)
        onView(withId(R.id.title_text)).check(matches(isDisplayed()))
        onView(withId(R.id.title_text)).check(matches(withText("Hello AndroidX World")))
        onView(withId(R.id.complete_checkbox)).check(matches(isDisplayed()))
        onView(withId(R.id.complete_checkbox)).check(matches(isChecked()))
    }
    @After
    fun tearDown() = mainCoroutineRule.runBlockingTest {
        ServiceLocator.resetRepository()
    }
}
FakeRepository class.
class FakeTasksRepository: TasksRepository {
    var tasksServiceData: LinkedHashMap<String,Task> = LinkedHashMap()
    private val observableTasks: MutableLiveData<Result<List<Task>>> = MutableLiveData()
    private var shouldReturnError: Boolean = false
    fun setReturnError(value: Boolean) {
        shouldReturnError = value
    }
    override fun observeTasks(): LiveData<Result<List<Task>>> {
        return observableTasks
    }
    override fun observeTask(taskId: String): LiveData<Result<Task>> {
        runBlocking { fetchAllToDoTasks() }
        return observableTasks.map { tasks ->
            when(tasks) {
                is Result.Loading -> Result.Loading
                is Result.Error -> Result.Error(tasks.exception)
                is Result.Success -> {
                    val task = tasks.data.firstOrNull() { it.id == taskId }
                        ?: return@map Result.Error(Exception("Not found"))
                    Result.Success(task)
                }
            }
        }
    }
    override suspend fun completeTask(id: String) {
        tasksServiceData[id]?.completed = true
    }
    override suspend fun completeTask(task: Task) {
        val compTask = task.copy(completed = true)
        tasksServiceData[task.id] = compTask
        fetchAllToDoTasks()
    }
    override suspend fun activateTask(id: String) {
        tasksServiceData[id]?.completed = false
    }
    override suspend fun activateTask(task: Task) {
        val activeTask = task.copy(completed = false)
        tasksServiceData[task.id] = activeTask
        fetchAllToDoTasks()
    }
    override suspend fun getTask(taskId: String): Result<Task> {
        if (shouldReturnError) return Result.Error(Exception("Test Exception"))
        tasksServiceData[taskId]?.let {
            return Result.Success(it)
        }
        return Result.Error(Exception("Could not find task"))
    }
    override suspend fun getTasks(): Result<List<Task>> {
        return Result.Success(tasksServiceData.values.toList())
    }
    override suspend fun saveTask(task: Task) {
        tasksServiceData[task.id] = task
    }
    override suspend fun clearAllCompletedTasks() {
        tasksServiceData = tasksServiceData.filterValues {
            !it.completed
        } as LinkedHashMap<String, Task>
    }
    override suspend fun deleteAllTasks() {
        tasksServiceData.clear()
        fetchAllToDoTasks()
    }
    override suspend fun deleteTask(taskId: String) {
        tasksServiceData.remove(taskId)
        fetchAllToDoTasks()
    }
    override suspend fun fetchAllToDoTasks(): Result<List<Task>> {
        if(shouldReturnError) {
            return Result.Error(Exception("Could not find task"))
        }
        val tasks = Result.Success(tasksServiceData.values.toList())
        observableTasks.value = tasks
        return tasks
    }
    override suspend fun updateLocalDataStore(list: List<Task>) {
        TODO("Not yet implemented")
    }
    fun addTasks(vararg tasks: Task) {
        tasks.forEach {
            tasksServiceData[it.id] = it
        }
        runBlocking {
            fetchAllToDoTasks()
        }
    }
}
Fragment class.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel.loadTaskById(args.taskId)
        setUpToast(this,viewModel.toastText)
        viewModel.editTaskEvent.observe(viewLifecycleOwner, {
            it?.let {
                val action = TaskDetailFragmentDirections
                    .actionTaskDetailFragmentToAddEditFragment(
                      args.taskId,
                        resources.getString(R.string.edit_task)
                    )
                findNavController().navigate(action)
            }
        })
        binding.editTaskFab.setOnClickListener {
            viewModel.editTask()
        }
    }
ViewModel class.
class TaskDetailViewModel(
    private val tasksRepository: TasksRepository
) : ViewModel() {
    private val TAG = "TaskDetailViewModel"
    private val _taskId: MutableLiveData<String> = MutableLiveData()
    private val _task = _taskId.switchMap {
        tasksRepository.observeTask(it).map { res ->
            Log.d("Test","res with value ${res.toString()}")
            isolateTask(res)
        }
    }
    val task: LiveData<Task?> = _task
    private val _toastText = MutableLiveData<Int?>()
    val toastText: LiveData<Int?> = _toastText
    private val _dataLoading = MutableLiveData<Boolean>()
    val dataLoading: LiveData<Boolean> = _dataLoading
    private val _editTaskEvent = MutableLiveData<Unit?>(null)
    val editTaskEvent: LiveData<Unit?> = _editTaskEvent
    fun loadTaskById(taskId: String) {
        if(dataLoading.value == true || _taskId.value == taskId) return
        _taskId.value = taskId
        Log.d("Test","loading task with id $taskId")
    }
    fun editTask(){
        _editTaskEvent.value = Unit
    }
    fun setCompleted(completed: Boolean) = viewModelScope.launch {
        val task = _task.value ?: return@launch
        if(completed) {
            tasksRepository.completeTask(task.id)
            _toastText.value = R.string.task_marked_complete
        }
         else {
            tasksRepository.activateTask(task.id)
            _toastText.value = R.string.task_marked_active
        }
    }
    private fun isolateTask(result: Result<Task?>): Task? {
        return if(result is Result.Success) {
            result.data
        } else {
            _toastText.value = R.string.loading_tasks_error
            null
        }
    }
    @Suppress("UNCHECKED_CAST")
    class TasksDetailViewModelFactory(
        private val tasksRepository: TasksRepository
    ): ViewModelProvider.NewInstanceFactory() {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return (TaskDetailViewModel(
                tasksRepository
            ) as T)
        }
    }
}
In this method in ViewModel when I comment out the below line of code all tests passed.
_toastText.value = R.string.loading_tasks_error
private fun isolateTask(result: Result<Task?>): Task? {
        return if(result is Result.Success) {
            result.data
        } else {
            _toastText.value = R.string.loading_tasks_error // Comment out this line then all test passed.
            null
        }
    }
