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 PMembeddedServer
Levi
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 PMexternalServices
AdamW
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 PMenvironment
Levi
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 externalServices
Levi
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