https://kotlinlang.org logo
Title
b

bbaldino

07/09/2020, 7:53 PM
I have some ktor server tests and ktor's server test harness involves executing the requests inside of a scope, which means my verification of the responses happens there. Looks something like this:
context("when the server") {
    withTestApplication({
        myServerModule()
    }) {
        context("receives a request for url/to/something") {
            with(handleRequest(<http://HttpMethod.Post|HttpMethod.Post>, "url/to/som ething") {
                should("return the correct response") {
                    response.status() shouldBe HttpStatusCode.OK
                }
            }
        }
    }
}
This doesn't work though, as where
should
is there isn't a coroutine scope and it complains. Is there a better way to do tests like this? Or would it be possible to have a non-suspend version of
should
?
I can wrap it
runBlocking
in the meantime, but that feels a bit hacky
s

sam

07/09/2020, 7:59 PM
are you on 4.1.1 ?
In 4.1.x we made all scopes be suspendable
b

bbaldino

07/09/2020, 7:59 PM
4.1.1, yeah
s

sam

07/09/2020, 8:00 PM
seems like maybe one was missed
does it work if you change should to test, and ShouldSpec to FunSpec ?
b

bbaldino

07/09/2020, 8:00 PM
Well, I think the scope is suspendable...at some layer...but I'm wrapped inside this ktor test harness scope
s

sam

07/09/2020, 8:00 PM
Actually I see
The with is changing the receiver probably, so you can't call should
try to reference the context lambda in a var. It might be ugly though
context("when the server") {
    withTestApplication({
        myServerModule()
    }) {
        context("receives a request for url/to/something") { context ->
            with(handleRequest(<http://HttpMethod.Post|HttpMethod.Post>, "url/to/som ething") {
                context.should("return the correct response") {
                    response.status() shouldBe HttpStatusCode.OK
                }
            }
        }
    }
}
l

LeoColman

07/09/2020, 8:03 PM
An alternative (which you probably thought of, but just in case) is to invert
with
and
should
1
And having ktor context as the last inner context
b

bbaldino

07/09/2020, 8:03 PM
Thanks @sam that's not too bad
@LeoColman I've got multiple should blocks for the request, so can't flip unfortunately 😕
l

LeoColman

07/09/2020, 8:04 PM
Yeah, that's what I thought
I believe KTor also has a way to execute that code without using
with
You can probably put ktor's receiver in a variable and operate with that inside your next scopes
b

bbaldino

07/09/2020, 8:06 PM
Actually that's a good idea @LeoColman! That might be best.
Even just saving the return of
handleRequest
rather than using
with
Ah, shit...it's not that part which is screwing it up
(Or, at least, not only that part)
It's the top level server part
the
withTestApplication
lambda is a receiver
s

sam

07/09/2020, 8:09 PM
Do the same trick and move the context -> up to the top scope
b

bbaldino

07/09/2020, 8:10 PM
Oh, wait, there is a non-suspending
should
?
Oh, but it doesn't take a TestContext block
s

sam

07/09/2020, 8:10 PM
don't mix up should scope with the should assertion
b

bbaldino

07/09/2020, 8:11 PM
Yeah I'm looking at
ShouldSpecContextScope.kt
there are 4 shoulds there, but yeah I guess the non-suspend ones are for something else
I'm also considering writing a non-suspend
should
function there which just calls the suspend one inside of
runBlocking
Ok, I think I'm gonna do that ☝️ @sam is it PR-worthy to add that?
s

sam

07/09/2020, 8:18 PM
The framework shouldn't be calling runBlocking at all
b

bbaldino

07/09/2020, 8:18 PM
ok
s

sam

07/09/2020, 8:18 PM
fun should(name: String) =
      TestWithConfigBuilder(TestName("should ", name), testContext, defaultConfig, xdisabled = false)

   fun xshould(name: String) =
      TestWithConfigBuilder(TestName("should ", name), testContext, defaultConfig, xdisabled = true)
b

bbaldino

07/09/2020, 8:18 PM
maybe
coroutineScope
would do it
s

sam

07/09/2020, 8:18 PM
Those two are non suspend because the test is passed into the second call on those
there are for
should("name").config() { }
b

bbaldino

07/09/2020, 8:19 PM
"into the second call"
what do you mean?
ah
does
ContainerScope#addTest
need to be suspend?
Nevermind, the real question is if
registerTestCase
needs to be suspend
s

sam

07/09/2020, 8:22 PM
Yes, because it ends up calling into the spec runners
b

bbaldino

07/09/2020, 8:22 PM
Ok
s

sam

07/09/2020, 8:22 PM
and that's getting in the heart of the execution of the tests, which are all coroutine based
b

bbaldino

07/09/2020, 8:26 PM
Hm, naming the context receiver doesn't actually work since it's a receiver, not an argument.
And naming it via
context("some test") test@ {
and then doing
this@test.should
doesn't work either
s

sam

07/09/2020, 8:27 PM
inside the context, then do
val context = this
b

bbaldino

07/09/2020, 8:28 PM
That still doesn't work, because it isn't within a coroutine body where I'm trying to call it
Hmph 😕
s

sam

07/09/2020, 8:29 PM
context("when the server") {
    val c1 = this
    withTestApplication({
        myServerModule()
    }) {
        c1.context("receives a request for url/to/something") {
            with(handleRequest(<http://HttpMethod.Post|HttpMethod.Post>, "url/to/som ething") {
                should("return the correct response") {
                    response.status() shouldBe HttpStatusCode.OK
                }
            }
        }
    }
}
b

bbaldino

07/09/2020, 8:30 PM
Ayyyyy. 🎉 Good idea
Oh, shit.
Same problem,
context
has to be called within a coroutine 😕
s

sam

07/09/2020, 8:33 PM
wrap it in a runblocking then
that's ok for your test case
b

bbaldino

07/09/2020, 8:34 PM
Yeah