José González Gómez
05/20/2021, 11:16 AMHttpClient
using a different engine? Let's say I have the following in my production code:
val DEFAULT_CLIENT = HttpClient() {
install(JsonFeature) {
serializer = KotlinxSerializer(kotlinx.serialization.json.Json {
ignoreUnknownKeys = true
})
}
}
and then in my testing code I have the following:
val client = HttpClient(MockEngine) {
engine {
addHandler { request ->
respond(...)
}
}
install(JsonFeature) {
serializer = KotlinxSerializer(kotlinx.serialization.json.Json {
ignoreUnknownKeys = true
})
}
}
As you can see, the client configuration must be the same, so I can reproduce the client behaviour in the tests, and then I configure the MockEngine
to respond whatever I want...
I have found the following:
• HttpClient
has a constructor which takes an engine
and a userConfig
, but
◦ userConfig
is declared as private val
and I can't find any public getter
◦ I don't know if I can create a shareable HttpClientConfig
, as
▪︎ I don't explicitly use an engine in the production code, as this is a multiplatform project (iOS / Android) and I let Ktor to select the default engine per platform
▪︎ HttpClientConfig
has a type parameter HttpClientEngineConfig
which seems to depend on the engine
• You have a config method, which lets you build a new HttpClient
with additional configuration, but you can't switch to a different engine using this
So the only way I can think of to be able to do this is try to declare a shared HttpClientConfig<*>.() -> Unit
fun
which then I can use in both instantiations.
Anyway this seems like a common use case with a bit convoluted solution. Is there any other simpler way to do what I want to do?fun
approach I've come up with this:
val CLIENT_CONFIGURATION: HttpClientConfig<*>.() -> Unit = {
install(JsonFeature) {
serializer = KotlinxSerializer(kotlinx.serialization.json.Json {
ignoreUnknownKeys = true
})
}
}
val DEFAULT_CLIENT = HttpClient {
apply(CLIENT_CONFIGURATION)
}
val TEST_CLIENT = HttpClient(MockEngine) {
apply(CLIENT_CONFIGURATION)
engine {
addHandler { request ->
respond(...)
}
}
}
It's even quite readable, as you apply a configuration to a client.
Any thoughts?russhwolf
05/20/2021, 12:41 PMclass ApiClient(engine: HttpClientEngine) {
private val httpClient = HttpClient(engine) {
// shared client config
}
}
Then in prod I pass the relevant platform engine like ApiClient(Ios.create()
or ApiClient(Android.create()
and in tests ApiClient(MockEngine { ... })
José González Gómez
05/20/2021, 12:47 PMMockEngine
... I didn't know you could do that, thanks!private val CLIENT_CONFIGURATION: HttpClientConfig<*>.() -> Unit = {
install(JsonFeature) {
serializer = KotlinxSerializer(kotlinx.serialization.json.Json {
ignoreUnknownKeys = true
})
}
}
private fun client(engine: HttpClientEngine? = null): HttpClient =
if (engine != null) HttpClient(engine) { apply(CLIENT_CONFIGURATION) }
else HttpClient { apply(CLIENT_CONFIGURATION) }
and then invoke whenever you need a client inside the API client.
And in the test code:
val mockEngine = MockEngine { request ->
respond(...)
}
and pass it as a parameter to the constructor / method.russhwolf
05/20/2021, 5:50 PMJosé González Gómez
05/21/2021, 3:30 PMMockEngine
or a real one) and configure it as you want