Skip to content

Instantly share code, notes, and snippets.

@ZakTaccardi
Created April 22, 2020 20:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ZakTaccardi/37dd346932fdf1409ca53b8e1743c61b to your computer and use it in GitHub Desktop.
Save ZakTaccardi/37dd346932fdf1409ca53b8e1743c61b to your computer and use it in GitHub Desktop.
Don't break the chain

PSA, don’t construct new CoroutineScopes and break structured concurrency in your classes.

class TransactionsDb(dispatchers: MyDispatchers) {
  private val scope = CoroutineScope(dispatchers.io)
}

This can cause tests to pass when an exception is thrown.

@Test 
fun loadTransactionsDbTest() = runBlocking<Unit> {
    val testScope: CoroutineScope = this
    val transactionsDb = TransactionsDb(
      _scope = testScope, 
      dispatchers = testScope.asDispatchers()
    )
    
    // if this function throws an exception, the test will pass because the new scope constructed internally
    // in `TransactionsDb` will swallow the exception and prevent it from propagating to the `runBlocking { }`
    transactionsDb.refreshTransactions() 
}

Instead, you should inject a CoroutineScope that hopefully comes from an object graph (ex: MyFeatureGraph).

class MyFeatureGraph {
  val scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
  val dispatchers = MyDispatchers()
  
  val transactionsDb: TransactionsDb = TransactionsDb(scope, dispatchers)
}

If you wish to change the dispatcher used by TransactionsDb, you can do something like the following and maintain structured concurrency:

class TransactionsDb(
  _scope: CoroutineScope,
  dispatchers: MyDispatchers
) : CoroutineScope by _scope + dispatchers.io

And now the previous example @Test will fail as expected if transactionsDb.refreshTransactions() throws an exception.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment