dave08
11/02/2021, 10:00 AMAleksei Tirman [JB]
11/02/2021, 10:16 AMTestApplicationEngine
doesn't use HttpClient
instance as well as a client's Mock engine doesn't use Ktor server.dave08
11/02/2021, 10:28 AM}
/**
* An instance of client engine user to be used in [client].
*/
val engine: HttpClientEngine = TestHttpClientEngine.create { app = this@TestApplicationEngine }
/**
* A client instance connected to this test server instance. Only works until engine stop invocation.
*/
val client: HttpClient = HttpClient(engine)
Aleksei Tirman [JB]
11/02/2021, 11:02 AMTestApplicationEngine
christophsturm
11/02/2021, 11:21 AMval server = TestApplicationEngine(environment = createTestEnvironment {
module {
...
}
}) {}.apply { start() }
dave08
11/02/2021, 11:46 AM@Test
fun `it ...`() = runBlocking {
// Arrange
....
// Act
val response = client....
// Assert
...
}
instead of having to nest everything over and over.christophsturm
11/02/2021, 11:58 AMAleksei Tirman [JB]
11/02/2021, 1:32 PM@Test
fun test() = withTestApplication {
// Arrange
with(application) {
routing {
get("/") {
call.respondText { "hello" }
}
}
}
// Act
val response = handleRequest(HttpMethod.Get, "/").response
// Assert
assertEquals(HttpStatusCode.OK, response.status())
assertEquals("hello", response.content)
}
dave08
11/02/2021, 1:53 PMin other words you want to set the server part up once and make requests in each test, correct?yes. What you called Arrange, isn't arrange for me, it's the SUT (subject under test), that doesn't change in the arrange step --- arrange has more to do with db setup and request inputs that get sent to the SUT or are the state the SUT is in... @Aleksei Tirman [JB]
handleRequest
doesn't have all the features of a regular HttpClient... and might not have all required plugins installed (at least the ones that make it similar to the ones the android app is using...)? So it seems to be harder to use... that's apart from the first problem I had of boilerplate code...// In main module
fun Application.appModule() {
install(...) { }
route(...) { ... }
}
In test class:
val client = HttpClient(TestAppEngine(::appModule))
Rustam Siniukov
11/02/2021, 2:25 PMAleksei Tirman [JB]
11/02/2021, 2:26 PMTestApplicationEngine
manually, assign the client to a test class property, and make requests in each test to the same server instance. Here is an example:
import io.ktor.application.*
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.testing.*
import kotlinx.coroutines.*
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
fun Application.module() {
routing {
get("/1") {
call.respondText { "hello" }
}
get("/2") {
call.respondText { "world" }
}
}
}
class SimpleTest {
private lateinit var engine: TestApplicationEngine
private lateinit var client: HttpClient
@BeforeTest
fun arrange() {
engine = TestApplicationEngine(createTestEnvironment())
engine.application.module()
client = engine.client
engine.start()
}
@AfterTest
fun stopServer() {
engine.stop(0L, 0L)
}
@Test
fun test1() = runBlocking {
val response = client.get<String>("/1")
assertEquals("hello", response)
}
@Test
fun test2() = runBlocking {
val response = client.get<String>("/2")
assertEquals("world", response)
}
}
dave08
11/02/2021, 2:33 PMclient = engine.client
where the client is being created by by the TestEngine, to use the TestEngine as the client's Engine (like instead of CIO, or OkHttp...), even in that ticket it seems like the proposition is going in the same direction in that sense... that way you stick to SRP (and dependency injection is easier... the same tests could be used with a real server running on a port, or the fake server...)Aleksei Tirman [JB]
11/02/2021, 2:36 PMdave08
11/02/2021, 2:37 PMAleksei Tirman [JB]
11/02/2021, 2:45 PMHttpClient
with a test server engine. In that case you can install any plugins for the client.
import io.ktor.application.*
import io.ktor.client.*
import io.ktor.client.features.*
import io.ktor.client.request.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.testing.*
import io.ktor.server.testing.client.*
import kotlinx.coroutines.*
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
fun Application.module() {
routing {
get("/1") {
call.respondText { "hello" }
}
get("/2") {
call.respondText { "world" }
}
get("/3") {
call.respondText { call.request.headers["custom"]!! }
}
}
}
class SimpleTest {
private lateinit var engine: TestApplicationEngine
private lateinit var client: HttpClient
@BeforeTest
fun arrange() {
engine = TestApplicationEngine(createTestEnvironment())
engine.application.module()
val clientEngine = TestHttpClientEngine.create { app = engine }
client = HttpClient(clientEngine) {
install(DefaultRequest) {
header("custom", "value")
}
}
engine.start()
}
@AfterTest
fun stopServer() {
engine.stop(0L, 0L)
}
@Test
fun test1() = runBlocking {
val response = client.get<String>("/1")
assertEquals("hello", response)
}
@Test
fun test2() = runBlocking {
val response = client.get<String>("/2")
assertEquals("world", response)
}
@Test
fun testPlugin() = runBlocking {
val response = client.get<String>("/3")
assertEquals("value", response)
}
}
dave08
11/02/2021, 2:50 PMclass KtorExtension(val module: Application.() -> Unit) : TestListener {
lateinit var appEngine: TestApplicationEngine
lateinit var clientEngine: HttpClientEngine
override suspend fun beforeTest(testCase: TestCase) {
appEngine = TestApplicationEngine(createTestEnvironment())
.also { it.application.module() }
clientEngine = TestHttpClientEngine.create { app = appEngine }
appEngine.start()
}
override suspend fun afterTest(testCase: TestCase, result: TestResult) {
appEngine.stop(0L, 0L)
}
}
Aleksei Tirman [JB]
11/02/2021, 6:31 PMDoes the engine have to be started after it's passed to the clientNo, you can start an engine before an assignment
Just wondering... would it be compatible with ktor client 2.0?Yes, it's compatible with Ktor 2.0.0. You just need to adjust imports of packages and API for getting the body of a response.