The documentation talks about dependency injection but does not really show how it is being done.
Documentation is not completed as well and has a bunch of place holders: http://ktor.io/getting-started.html
I tried to create my main function in a way that it accepts parameter (which is my dependency) but that failed on the test side when I call withTestApplication
.
I looked into the application code and saw that Application accepts a configuration object but I have no idea how I can change that configuration object to inject some dependencies inside of it.
package org.jetbrains.ktor.application
/**
* Represents configured and running web application, capable of handling requests
*/
class Application(val environment: ApplicationEnvironment) : ApplicationCallPipeline() {
/**
* Called by host when [Application] is terminated
*/
fun dispose() {
uninstallAllFeatures()
}
}
/**
* Convenience property to access log from application
*/
val Application.log get() = environment.log
In the test code using withTestApplication
I have something similar to the below:
@Test
internal fun myTest() = withTestApplication (Application::myMain)
The above withTestApplication
would fail if I call myMain
with parameters (parameters that I need to mock and inject.)
Update:
The issue is that in my request handling, I am using a dependency class that connects to other web services outside and does some requests, I need a way to be able to inject this so in my tests I can stub/mock it and change its behavior based on my test cases.
Ktor is a lightweight framework for building asynchronous servers and clients in connected systems using the Kotlin programming language. Similar to Quarkus, Ktor provides very fast startup times (~60% faster than Spring) and low memory usage (~50% less than Spring).
What's KTor Client and how is it different from Retrofit for Android? Ktor client is an HTTP client that can be used for making requests and handling responses. It works pretty similar to Retrofit but what makes it stand out is that it is not wired to anything android specific and is completely Kotlin powered.
There are dozens of production-ready libraries created by the Ktor team and community, which should cover most of your needs. You can discover the big part of it using the official Ktor Project Generator.
Easy example with Koin
1) At first, define our prod and test dependencies:
val prodModule = module {
single<IFirstService> { RealFirstService() }
single<ISecondService> { RealSecondService() }
}
val testModule = module {
single<IFirstService> { FakeFirstService() }
single<ISecondService> { FakeSecondService() }
}
2) Then add DI initialization before app start:
fun main(args: Array<String>) {
startKoin(listOf(prodModule))
embeddedServer(Netty, commandLineEnvironment(args)).start(true)
}
3) Use inject in Application or in routes:
fun Application.apiModule() {
val firstService: IFirstService by inject()
val secondService: ISecondService by inject()
...
routing {
someApi(inject(), inject())
}
}
4) (Optional) For tests just add initialization in testModule before run test:
fun testApp(test: TestApplicationEngine.() -> Unit) {
withTestApplication({
... // configure your test app here
stopKoin() // Need to stop koin and restart after other tests
startKoin(listOf(testModule)) // Init with test DI
apiModule() // Run you application
})
}
// And run tests
@Test
fun `get events`() = testApp {
// do tests
}
That's all!
Ktor doesn't have a built-in dependency injection mechanism. If you need to use DI, you will need to use any framework you like, such as Guice for example. It would look something like this:
fun Application.module() {
Guice.createInjector(MainModule(this))
}
// Main module, binds application and routes
class MainModule(private val application: Application) : AbstractModule() {
override fun configure() {
bind(Application::class.java).toInstance(application)
... other bindings ...
}
}
This way you delegate application composition to Guice and build it up as any other application. E.g. you might compose different parts of your application like this:
class Hello @Inject constructor(application: Application) {
init {
application.routing {
get("/") {
call.respondText("Hello")
}
}
}
}
and then bind it in a main module:
bind(Hello::class.java).asEagerSingleton()
asEagerSingleton
is needed so that Guice will create it eagerly since no other service would query it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With