Search code examples
kotlinkotlin-coroutinesmockwebserver

Is there a way to guarantee coroutine execution order when using coroutineScope and launch?


I have some code I want to test that uses structured concurrency in Kotlin:

suspend fun logic(): Something = coroutineScope {
    launch { taskOne() }
    launch { taskTwo() }
    launch { taskThree() }
    launch { taskFour() }

    Something(...)
}

The tasks perform an HTTP request using a web client.

In my tests I'm using MockWebServer:

val server = MockWebServer()

@Test
fun logicTest() {
    server.enqueue(MockResponse().setResponseCode(200).setBody(read("one.json")))
    server.enqueue(MockResponse().setResponseCode(200).setBody(read("two.json")))
    server.enqueue(MockResponse().setResponseCode(200).setBody(read("three.json")))
    server.enqueue(MockResponse().setResponseCode(200).setBody(read("four.json")))

    val result = runBlocking { logic() }

    result shouldBe Something(...)
}

The problem is sometimes when running all my tests (about 30) the coroutines are called in the "wrong" order, which is fine in prod but for tests it breaks because of the queueing nature of MockWebServer. For example sometimes the order is:

taskOne()
taskThree()
// next tasks fail because taskThree throws when given the response "two.json"

I want to know if there is a way to guarantee coroutine execution order in tests. This shouldn't modify the prod code, as the order is not important except in tests! So can't use Mutex or Channel.

I tried using runTest and runBlocking(Dispatchers.Default.limitedParallelism(1)). Also tried runBlocking(Executors.newSingleThreadExecutor().asCoroutineDispatcher()).

A possible workaround is to use MockWebServer's Dispatcher but I really want to keep the queuing as it's easier to understand and contains information about the expected flow of requests.

Another possible workaround is to delay the mock server's responses using MockResponse.throttleBody().


Solution

  • I ended up writing a DSL to make working with MockWebServer's Dispatcher easier. https://github.com/akhial/mock-server-dsl

    val server = MockWebServer()
    
    val dispatcher = mockResponses {
        matchBody("foo") { MockResponse().setResponseCode(201) }
        matchPath("bar") { MockResponse().setResponseCode(200).setBody("baz") }
        matchMethod("GET") { MockResponse().setResponseCode(403) }
        matchMethod("POST") { MockResponse().setResponseCode(200) }
    }
    
    server.dispatcher = dispatcher