Levi
12/21/2023, 4:04 PMexternalServices to mock external services only work for client requests made directly with the client provided in testApplication? In other words, if in your test you call some of your application code which internally creates a client, will the requests made by that client not use the mock setup?Aleksei Tirman [JB]
12/21/2023, 4:47 PMDoes usingYes and only with that client.to mock external services only work for client requests made directly with the client provided inexternalServices?testApplication
Levi
12/21/2023, 4:55 PMAuth0Client. The Auth0Client, in turn, takes an HttpClient. I thought this would make it easy to test those routes by using the test client available in my test.
I have routes:
fun Application.userRoutes(auth0Client: Auth0Client, residentCache: KafkaResidentCache) {
routing {
route("/user") {
sendOtpEmailRoute(auth0Client)
validateEmailOtpRoute(auth0Client, residentCache)
sendSmsOtpRoute(auth0Client)
authenticateUserRoute(auth0Client)
completeRegistrationRoute(auth0Client, residentCache)
}
}
}
and then my main() and `Application.module()`:
fun main(args: Array<String>) = io.ktor.server.netty.EngineMain.main(args)
fun Application.module(auth0Client: Auth0Client = Auth0Client(environment.config),
residentCache: KafkaResidentCache = KafkaResidentCache()) {
install(ContentNegotiation) { json() }
....
userRoutes(auth0Client, residentCache)
}
where Auth0Client is:
class Auth0Client(val auth0Domain: String, val clientId: String, val clientSecret: String, val client: HttpClient) {
constructor(
config: ApplicationConfig
) : this(
config.property("auth0.domain").getString(),
config.property("auth0.clientId").getString(),
config.property("auth0.clientSecret").getString(),
HttpClient(Apache5) {
engine {
followRedirects = true
socketTimeout = SOCKET_TIMEOUT
connectTimeout = CONNECTION_TIMEOUT
connectionRequestTimeout = CONNECTION_REQUEST_TIMEOUT
}
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.ALL
}
install(ContentNegotiation) { json() }
},
)
...
...
I'm unable to figure out how to, in my test, pass in a test implementation of the Auth0Client which uses the test client so then I can test those routes without actually making calls to Auth0.
I thought I could do something like application { module(auth0Client, kafkaResidentCache) } in my testApplication but that doesn't seem to work.
Appreciate any guidance or advice you can offer.Levi
12/21/2023, 4:57 PMapplication { module(auth0Client, kafkaResidentCache) } was:
io.ktor.server.application.DuplicatePluginException: Please make sure that you use unique name for the plugin and don't install it twice. Conflicting application plugin is already installed with the same key as `ContentNegotiation`Levi
12/21/2023, 5:04 PMembeddedServer?Levi
12/21/2023, 5:28 PMapplication {
modules = [ org.wol.ApplicationKt.module ]
}
which means when calling application {...} in my test that things get loaded/started twiceLevi
12/21/2023, 5:28 PMembeddedServer and removing that from application.conf, but I don't want to use embeddedServer 😩Levi
12/21/2023, 5:29 PMembeddedServer just for the purposes of tests? Or there is some other approach for easily being able to configure modules during tests but still using EngineMain?AdamW
12/21/2023, 5:30 PMapplication { module(…) } with test dependencies. I would try to debug that exception still 🤔
edit: whoops, you already figured it outLevi
12/21/2023, 5:30 PMembeddedServerLevi
12/21/2023, 5:30 PMLevi
12/21/2023, 5:30 PMembeddedServer?AdamW
12/21/2023, 5:31 PMAdamW
12/21/2023, 5:31 PMLevi
12/21/2023, 5:31 PMLevi
12/21/2023, 5:31 PMLevi
12/21/2023, 5:37 PMtestApplication {
val host = "myhost"
val client = createClient {
install(ContentNegotiation) { json() }
}
val auth0Client = Auth0Client(host, "", "", client)
application { module(auth0Client, kafkaResidentCache) }
val expected = """{"key": "value"}"""
externalServices {
this@testApplication.install(io.ktor.server.plugins.contentnegotiation.ContentNegotiation) {
json()
}
hosts("https://$host") {
routing { post("passwordless/start") { call.respond(expected) } }
}
}
val response =
<http://client.post|client.post>("/user/otp-email") {
// install(ContentNegotiation) { json() }
contentType(ContentType.Application.Json)
setBody("""{"email": "<mailto:someemail@example.com|someemail@example.com>"}""")
}
response.bodyAsText() shouldBe expected
}
This works if I use
fun main() {
embeddedServer(Netty, port = 8081, module = Application::module).start(wait = true)
}
and remove the modules = [...] from my application.conf, but if I use:
fun main(args: Array<String>) = io.ktor.server.netty.EngineMain.main(args)
it will fail due to
io.ktor.server.application.DuplicatePluginException: Please make sure that you use unique name for the plugin and don't install it twice. Conflicting application plugin is already installed with the same key as `RequestValidation`Levi
12/21/2023, 5:38 PMmodules = [...] and that might resolve the issueLevi
12/21/2023, 5:38 PMAdamW
12/21/2023, 5:39 PMenvironment { } by hand. In my case it’s actually desired, and seems to override the file-based config.Levi
12/21/2023, 5:40 PMenvironment {
config = MapApplicationConfig()
}Levi
12/21/2023, 5:44 PMLevi
12/21/2023, 5:44 PMLevi
12/21/2023, 5:45 PMLevi
12/21/2023, 5:45 PMinstall(ContentNegotiation) { json() } in my Application.module(). Doesn't work if I keep it in.AdamW
12/21/2023, 5:45 PMLevi
12/21/2023, 5:46 PMio.ktor.server.application.DuplicatePluginException: Please make sure that you use unique name for the plugin and don't install it twice. Conflicting application plugin is already installed with the same key as `ContentNegotiation`AdamW
12/21/2023, 5:46 PMtestApplication block?Levi
12/21/2023, 5:46 PMexternalServices blockLevi
12/21/2023, 5:47 PMLevi
12/21/2023, 5:47 PMexternalServicesAdamW
12/21/2023, 5:47 PMexternalServices before 😅AdamW
12/21/2023, 5:48 PMLevi
12/21/2023, 5:49 PMLevi
12/21/2023, 5:49 PMLevi
12/21/2023, 5:50 PMenvironmentLevi
12/21/2023, 5:51 PMAdamW
12/21/2023, 5:53 PMLevi
12/21/2023, 5:54 PMLevi
12/21/2023, 5:54 PMAdamW
12/21/2023, 5:57 PMHttpClient(MockEngine { … }) instead of using createClient. There’s a bit of annoying boilerplate mocking services this way, so I might look into externalServicesLevi
12/21/2023, 5:57 PMLevi
12/21/2023, 5:58 PMLevi
12/21/2023, 9:04 PMDependencies constructLevi
12/21/2023, 9:05 PMresourceScope from Arrow