Patrick Ramsey
08/23/2021, 11:10 PMAlexandre Brown
08/24/2021, 1:57 AMPatrick Ramsey
08/24/2021, 4:38 PMAlexandre Brown
08/24/2021, 4:56 PMclass MainViewModelTest {
@ExperimentalCoroutinesApi
private val testDispatcher = TestCoroutineDispatcher()
@Before
fun setup() {
// 1
Dispatchers.setMain(testDispatcher)
}
@After
fun tearDown() {
// 2
Dispatchers.resetMain()
// 3
testDispatcher.cleanupTestCoroutines()
}
@JvmField
@RegisterExtension
val coroutineTestExtension = CoroutineTestExtension()
@BeforeEach
fun setup() {
myObjectIWantToTest = MyObject(coroutineTestExtension.testDispatcher)
}
You can even have a base class that uses SupervisorJob in conjonction with the provided coroutine context
class SupervisedCoroutineScopeBehavior(
private val baseContext: CoroutineContext
) : SupervisedCoroutineScope {
override val coroutineContext: CoroutineContext
get() = baseContext + supervisingJob
private val supervisingJob = SupervisorJob()
override fun cancelSupervisedJob() {
supervisingJob.cancel()
}
override fun cancelCoroutines() {
supervisingJob.cancelChildren()
}
This way child classes provide the base context (in test TestDispatcher, in prod, Dispatchers.Default or IO for instance) yet the class can control its scope lifecycle.
The important thing to note here is that we pass the coroutine context via the constructor.Patrick Ramsey
08/24/2021, 5:10 PMAlexandre Brown
08/24/2021, 8:36 PMcollect
therefore the test will throw an exception saying that some coroutines are not done when using with runBlockingTest
.
I know this is not quite what you are looking for but to my knowledge (and please if someone knows more please tell us), this is the only way to have this assurance.Patrick Ramsey
08/24/2021, 8:37 PMAlexandre Brown
08/24/2021, 8:38 PMPatrick Ramsey
08/24/2021, 8:39 PMAlexandre Brown
08/24/2021, 8:39 PMPatrick Ramsey
08/24/2021, 8:39 PMAlexandre Brown
08/24/2021, 8:43 PMPatrick Ramsey
08/24/2021, 8:43 PMAlexandre Brown
08/24/2021, 8:46 PMPatrick Ramsey
08/24/2021, 8:46 PMAlexandre Brown
08/24/2021, 8:50 PMclass CoroutineTestExtension(
val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
) : BeforeEachCallback, AfterEachCallback, TestCoroutineScope by TestCoroutineScope(testDispatcher) {
override fun beforeEach(context: ExtensionContext?) {
Dispatchers.setMain(testDispatcher)
}
override fun afterEach(context: ExtensionContext?) {
cleanupTestCoroutines()
Dispatchers.resetMain()
}
}
Patrick Ramsey
08/24/2021, 8:58 PMAlexandre Brown
08/24/2021, 9:05 PMPatrick Ramsey
08/24/2021, 9:07 PMAlexandre Brown
08/24/2021, 9:08 PMPatrick Ramsey
08/24/2021, 9:08 PMJoe
08/25/2021, 2:10 AMtoString()
of coroutineContext[CoroutineDispathcer]
to make sure things run on the right dispatcher. Assume this will break eventually but has worked for now. example test code: https://github.com/trib3/leakycauldron/blob/6229b4fa720c29cc4167b832c3dc2014a85476[…]in/com/trib3/server/coroutine/CoroutineInvocationHandlerTest.ktPatrick Ramsey
08/25/2021, 2:11 AM