In the ktor
application I have a route that starts a session for the created resource that launches a job to manage interactions with that session and auto-terminates the session if there are no interactions for a while
// routing
post("/sessions") {
val sessionName = call.receive<NewSessionRequest>().name
val session = Sessions.newSession(sessionName) // Companion object that creates a session
launch {
var sessionExists = true
while (sessionExists) {
sessionExists = withTimeoutOrNull(session.timeToLive) {
session.lifeChannel.receive()
} ?: false
}
session.close()
Sessions.remove(session)
}
call.respond(HttpStatusCode.Created, sessionName)
}
post("/sessions/{name}") {
// other call that pings the sessions lifeChannel
}
and then some other route that triggers a message to be sent to the session
's lifeChannel
to keep the session alive.
In normal operation this works quite well and the session is kept alive as desired. However, during testing the test waits until the entire launched job completes before continuing.
@Test
fun `user can create room`() = testApplication {
val response = jsonClient.post("/sessions") {
contentTupe(ContentType.Application.Json)
setBody(NewSessionRequest("sess"))
}
assertEquals(HttpStatusCodes.created, response.status)
}
Will wait until the job completes and the session terminates before completing the test.
How can I get the testApplication to ignore or work outside of the coroutine context of the application as a normal http call would do with a real ktor app? Is this a bad practice as testing for it is non-intuitive?
Adding a simple test I want to have pass
class CoroutineRoutesTest {
@Test
fun `how to deal with launched coroutine`() {
testApplication {
application {
routing {
get("/launch-job") {
val delay = 1.seconds
launch {
delay(delay)
}
call.respond("Job lasting for $delay")
}
}
}
val response: HttpResponse
val timing = measureTimeMillis {
response = client.get("/launch-job")
}
assertEquals(HttpStatusCode.OK, response.status)
LoggerFactory.getLogger("CoroutineTest").info("response time: $timing ms")
assertTrue(timing < 800)
}
}
}
I can make your last test pass by launching a job from a different coroutine scope. I'm not sure whether this is the desired behavior or not.
@Test
fun `how to deal with launched coroutine`() {
val scope = CoroutineScope(Dispatchers.Default)
testApplication {
application {
routing {
get("/launch-job") {
val delay = 1.seconds
scope.launch {
delay(delay)
}
call.respond("Job lasting for $delay")
}
}
}
// ...
}
}