How to Inject Other Dispatchers to MainCoroutineRule?

In the Google Codelab for Coroutines, we are shown a MainCoroutineScopeRule. Within the rule, it explains that this rule can be extended to other dispatchers in addition to Dispatchers.Main:

override fun starting(description: Description?) {
    super.starting(description)
    // If your codebase allows the injection of other dispatchers like
    // Dispatchers.Default and Dispatchers.IO, consider injecting all of them here
    // and renaming this class to `CoroutineScopeRule`
    //
    // All injected dispatchers in a test should point to a single instance of
    // TestCoroutineDispatcher.
    Dispatchers.setMain(dispatcher)
}

My question is, how exactly are we to inject the other dispatchers? Does this assume that we’re using dependency injection? If so, what if I’m not using DI, can I still extend this rule to the other dispatchers? I don’t see anything in the kotlinx-coroutines-test library that allows me to set the TestCoroutineDispatcher to the other dispatchers. So, there’s this:

Dispatchers.setMain(dispatcher)

…but not this:

Dispatchers.setIO(dispatcher) // Or Default, etc.

Am I instead expected to rewrite my suspend functions to take in a dispatcher as a parameter:

suspend doSomeIO(dispatcher: CoroutineDispatcher = Dispatchers.IO) {
    launch(dispatcher) {
        // Some long-running IO operation
    }
}

Answer

You are correct in that this does assume you are injecting your dispatchers. If you are not using the main dispatcher, you should be injecting the dispatcher in order to test it properly.

The way you wrote the suspend function is one way to do it, if you want to force that particular function to be on the Dispatchers.IO thread. However, then you will end up having nested launches.

Instead of that, I would just pass the dispatcher in to a viewModel, and let the viewmodel decide how to call the suspend function.

//Your version:
  
suspend fun doSomeIO(dispatcher: CoroutineDispatcher = Dispatchers.IO) {
    launch(dispatcher) {
        // Some long-running IO operation
    }
}

class MyViewModel(val dispatcher: CoroutineDispatcher = Dispatchers.IO: ViewModel() {

  init {
    viewModelScope.launch {
      doSomeIO(dispatcher) // here you are launching one coroutine inside the other
    }
  }
}

// Instead try this: 

suspend fun doSomeIO() {
        // Some long-running IO operation
}

class MyViewModel(val dispatcher: CoroutineDispatcher = Dispatchers.IO: ViewModel() {

  init {
    viewModelScope.launch(dispatcher) {
      doSomeIO()
    }
  }
}

Leave a Reply

Your email address will not be published. Required fields are marked *