triplem
triplem

Reputation: 1334

Ktor / Kodein - How to write Integration Tests

Currently I write a small demo-app which uses Ktor as its Application Environment and Kodein as the Dependency Injection Framework.

During the initialization of the Application I do import some modules, one of those I would like to replace during the initialization of the Integration Tests:

fun Application.module(testing: Boolean = false) {
logger.debug { "Starting main" }

restModule()

di {
  bind<Json>() with singleton {
    Json {
      ...
    }
  }

  import(persistenceModule)
}

In the test, I would like to use a different persistenceModule, say eg. a MemoryModule. My tests are initialized like:

fun start() {
  val configPath = ClassLoader.getSystemResource("application-acceptanceTest.conf").file
  engine = embeddedServer(CIO, commandLineEnvironment(arrayOf("-config=$configPath")))
  engine.start()

  val disposable = engine.environment.monitor.subscribe(ApplicationStarted) { application: Application ->
    started = true
  }

  while (!started) {
    Thread.sleep(10)
  }
  disposable.dispose()
}

I have tried already to call

engine.application.di

but this gives me (quite obviously) only access to the Ktor Feature, which is already initialized. Is anything like this possible at all?

Upvotes: 1

Views: 991

Answers (1)

romainbsl
romainbsl

Reputation: 544

Kodein-DI allows you to override dependencies. Regarding the following interface:

interface Repository {
    fun save()
    fun find()
}

You can have a production implementation, included in its own DI module:

class PersistenceRepository : Repository {
    /* implementation */
}

val persistenceModule = DI.Module("persistenceModule") {
    bind<Repository>() with singleton { PersistenceRepository() }
}

and also a test implementation of that same interface:

class MemoryRepository : Repository {
    /* implementation */
}

val memoryModule = DI.Module("memoryModule") {
    bind<Repository>(overrides = true) with singleton { MemoryRepository() }
}

Note the overrides parameter that needs to be explicit.

You can pass a DI container to your Ktor function:

val mainDI = DI {
    import(persistenceModule)
}

fun Application.main(di: DI) {
    di { extend(di) }
}

And extend the mainDI in your tests, to override the proper bindings with the memoryModule:

class ApplicationTest {
    val testDI = DI {
        extend(mainDI)
        import(memoryModule, allowOverride = true)
    }
    
    @Test
    fun myTest() {
        withTestApplication({ main(testDI) })
        // ...
    }
}

Upvotes: 1

Related Questions