Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unit test function that has coroutine `GlobalScope.launch`

I have this function

    override fun trackEvent(trackingData: TrackingData) {
        trackingData.eventsList()
    }

And I could have my test as below.

    @Test
    fun `My Test`() {
        // When
        myObject.trackEvent(myTrackingMock)

        // Then
        verify(myTrackingMock, times(1)).eventsList()
    }

However, if I make it into a

    override fun trackEvent(trackingData: TrackingData) {
        GlobalScope.launch{
            trackingData.eventsList()
        }
    }

How could I still get my test running? (i.e. can make the launch Synchronous?)

like image 934
Elye Avatar asked Aug 14 '19 07:08

Elye


2 Answers

To approach the answer, try asking a related question: "How would I unit-test a function that has

Thread { trackingData.eventsList() }

in it?"

Your only hope is running a loop that repeatedly checks the expected condition, for some period time, until giving up and declaring the test failed.

When you wrote GlobalScope.launch, you waived your interest in Kotlin's structured concurrency, so you'll have to resort to unstructured and non-deterministic approaches of testing.

Probably the best recourse is to rewrite your code to use a scope under your control.

like image 139
Marko Topolnik Avatar answered Nov 12 '22 06:11

Marko Topolnik


I created my own CoroutineScope and pass in (e.g. CoroutineScope(Dispatchers.IO) as a variable myScope)

Then have my function

    override fun trackEvent(trackingData: TrackingData) {
        myScope.launch{
            trackingData.eventsList()
        }
    }

Then in my test I mock the scope by create a blockCoroutineScope as below.

   class BlockCoroutineDispatcher : CoroutineDispatcher() {
        override fun dispatch(context: CoroutineContext, block: Runnable) {
            block.run()
        }
    }

    private val blockCoroutineScope = CoroutineScope(BlockCoroutineDispatcher())

For my test, I'll pass the blockCoroutineScope in instead as myScope. Then the test is executed with launch as a blocking operation.

like image 35
Elye Avatar answered Nov 12 '22 04:11

Elye