Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to inject dependencies in a ktor Application

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.

like image 701
Mehdi Karamosly Avatar asked Sep 12 '17 20:09

Mehdi Karamosly


People also ask

Is KTOR better than spring boot?

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).

Is KTOR better than retrofit?

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.

Is KTOR production ready?

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.


2 Answers

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!

like image 185
Pavel Shorokhov Avatar answered Sep 27 '22 23:09

Pavel Shorokhov


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.

like image 34
Ilya Ryzhenkov Avatar answered Sep 27 '22 23:09

Ilya Ryzhenkov