I'm trying to test internal and external ports in ...
# ktor
d
I'm trying to test internal and external ports in Ktor 3, how do I configure the testApplication to listen on those ports?
I'm using:
Copy code
val client = createClient {
        install(ContentNegotiation) { json() }
        defaultRequest {
            host = "0.0.0.0"
            port = 8080
        }
    }
for the client.
And
localPort
for internal requests
a
The
testApplication
doesn't listen for connections, but instead, it uses its internal client to forward the requests to the test server.
d
So how does it react when there's localPort in the routes?
There's no way to simulate the distinction with the client making "as if" it's using the internal port?
Or does it just ignore them? It seems like in Ktor 2 my tests were running with the above client, now when I migrated my tests starting failing, so it seems like the behaviour changed...?
a
Can you please share an example of the test?
d
Copy code
fun testApp(
    token: String = "",
    repository: AccountRepository? = null,
    block: suspend TestContext.(HttpClient) -> Unit
) = testApplication {
    val deviceStateInRequest = Arb.deviceState()()
    val processSyncRequestFake = ProcessSyncRequestFake()

    application {
        configureSerialization()
        repository?.let { internalRoutes(it) }
        externalRoutes(processSyncRequestFake, token)
    }

    val client = createClient {
        install(ContentNegotiation) { json() }
        defaultRequest {
            host = "0.0.0.0"
            port = 8080
        }
    }

    TestContext(this, deviceStateInRequest, processSyncRequestFake).block(client)
}

...

        it("internally retrieves list of foo") {
            testApp(repository = accountRepo) {

                val result = it.get("/foo") {
                    host = "0.0.0.0"
                    port = 8090
                }.body<JsonElement>()

...
I get this error:
Copy code
Can not resolve request to <http://0.0.0.0:8090>. Main app runs at localhost:80, localhost:443 and external services are
io.ktor.server.testing.client.InvalidTestRequestException: Can not resolve request to <http://0.0.0.0:8090>. Main app runs at localhost:80, localhost:443 and external services are
	at io.ktor.server.testing.client.DelegatingTestClientEngine.execute(DelegatingTestClientEngine.kt:62)
	at io.ktor.server.testing.client.DelegatingTestClientEngine$execute$1.invokeSuspend(DelegatingTestClientEngine.kt)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
	at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:113)
	at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
I want to make sure that internal endpoints are not accidentally exposed so that's part of the reason ports are important in these tests... then Kubernetes doesn't expose those ports to the outside, only inside the cluster.
I don't have any application.conf
a
Can you please share a self-contained test (without unresolved dependencies) to reproduce the problem?
d
Copy code
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.testing.*
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals

class KtorTest {
    @Test
    fun test() = testApplication {
        application {
            this.routing {
                localPort(8080) {
                    get("/") {
                        call.respondText("Hello, world!")
                    }
                }
            }
        }

        val client = createClient {
            defaultRequest {
                host = "0.0.0.0"
                port = 8080
            }
        }

        val result = client.get("/")

        assertEquals("Hello, world!", result.bodyAsText())
    }
}
Copy code
2025-01-27 13:34:25.217 [Thread-14] INFO  org.eclipse.jetty.server.Server - Stopped Server@43e065f2{STOPPING}[11.0.15,sto=1000]
2025-01-27 13:34:25.218 [Thread-14] INFO  org.eclipse.jetty.server.Server - Shutdown Server@43e065f2{STOPPING}[11.0.15,sto=1000]
2025-01-27 13:34:25.229 [Thread-14] INFO  o.e.jetty.server.AbstractConnector - Stopped NetworkTrafficServerConnector@b835727{HTTP/1.1, (http/1.1, h2c)}{0.0.0.0:45891}
2025-01-27 13:34:25.231 [Thread-14] INFO  o.e.j.server.handler.ContextHandler - Stopped o.e.j.s.ServletContextHandler@4b4ee511{/,null,STOPPED}
2025-01-27 13:34:25.231 [Thread-14] INFO  o.e.j.server.handler.ContextHandler - Stopped o.e.j.s.ServletContextHandler@58b91d57{/__admin,null,STOPPED}
2025-01-27 13:34:25.310 [DefaultDispatcher-worker-3 @call-context#4] INFO  io.ktor.test - No ktor.deployment.watch patterns specified, automatic reload is not active.
2025-01-27 13:34:25.330 [DefaultDispatcher-worker-3 @call-context#4] INFO  io.ktor.test - Application started in 0.031 seconds.
2025-01-27 13:34:25.332 [DefaultDispatcher-worker-1 @coroutine#5] INFO  io.ktor.test - Responding at <http://localhost:80>
2025-01-27 13:34:25.332 [DefaultDispatcher-worker-1 @coroutine#5] INFO  io.ktor.test - Responding at <https://localhost:443>

Can not resolve request to <http://0.0.0.0:8080>. Main app runs at localhost:80, localhost:443 and external services are 
io.ktor.server.testing.client.InvalidTestRequestException: Can not resolve request to <http://0.0.0.0:8080>. Main app runs at localhost:80, localhost:443 and external services are 
	at io.ktor.server.testing.client.DelegatingTestClientEngine.execute(DelegatingTestClientEngine.kt:62)
	at io.ktor.server.testing.client.DelegatingTestClientEngine$execute$1.invokeSuspend(DelegatingTestClientEngine.kt)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
	at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:113)
	at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
@Aleksei Tirman [JB] Here's an example w/o dependencies
It seems that I was missing this:
Copy code
fun ApplicationEngine.Configuration.envConfig() {
    connector {
        host = "0.0.0.0"
        port = 8080
    }
    connector {
        host = "0.0.0.0"
        port = 8090
    }
}
in the
engine { envConfig() }
block... I'm wondering if it really considers that, taking into account what you said that the test application doesn't actually listen on any ports... I didn't see a word of all this mentioned in the testing docs, it would really be nice if it would be there.
It seems to yes distinguish between them... now that I just ran my real tests.