kotlindependency-injectionktorkoin

Inject a koin database into a ktor testapplication


My application is set up to load up the database immediately the server starts to run with createdAtStart = true

val dbModule = module {
    single(createdAtStart = true) {
        Db(
            driverClassName = "org.h2.Driver", jdbcURL = "jdbc:h2:file:./build/db"
        )
    }

when running the server normally, i do not need to manually inject the database anywhere to initialize it as it is done automatically and dependents on the db object obtain

also, the db used for testing is an in-memory database and is inject in unit test using koin easily

factory(qualifier = named("test")) { params ->
        Db(
            driverClassName = "org.h2.Driver",
            jdbcURL = "jdbc:h2:mem:${params.get<String>()};DB_CLOSE_DELAY=-1"
        )
    }

the question

in testing the application as a whole, i.e the server routes using ktor's testApplication, the default database is injected directly and I cannot inject the test db.

// test is empty because i realised i can't inject my test db
class ApplicationTest: KoinTest {


    @Test
    fun testRoot() = testApplication {



        val response = client.get("/")
        assertEquals(HttpStatusCode.OK, response.status)
    }
}

I'm thinking there's a flaw in my app design and file structure and all that as this is my first time using ktor

how do i get the right db into the test application


Solution

  • I had to restructure my code base a bit.

    The main database is no longer eargerly created

    // koin.kt
    val dbModule = module {
        single {
            Db(
                driverClassName = "org.h2.Driver", jdbcURL = "jdbc:h2:file:./build/db"
            )
        }
    
        factory(qualifier = named("test")) { params ->
            Db(
                driverClassName = "org.h2.Driver",
                jdbcURL = "jdbc:h2:mem:${params.get<String>()};DB_CLOSE_DELAY=-1"
            )
        }
    }
    

    Dependency injection and database configuration were separated into different modules and used based on a testing flag on the main module

    // application.kt
    
    fun Application.di() {
        install(Koin) {
            slf4jLogger()
            modules(dbModule)
        }
    }
    
    fun Application.configureDb() {
        val db: Db by inject()
        TransactionManager.defaultDatabase = db.database // this line isn't really need as exposed uses last open db connection
    }
    
    fun Application.main(testing: Boolean = false) {
        if (!testing) {
            di()
            configured()
        }
    }
    
    
    class ApplicationTest : KoinTest {
        private lateinit var db: Db
    
        @BeforeEach
        fun `setup db`() {
            db = get {
                parametersOf("")
            }
    
            TransactionManager.defaultDatabase = db.database
        }
    
        @Test
        fun testRoot() = testApplication {
    
            application {
                module(testing = true)
            }
    
            environment {
                config =
                    ApplicationConfig("test_application.yaml") // loads environment variables for test
    
    
            val response = client. Get("/")
            assertEquals(HttpStatusCode.OK, response.status)
        }
    
        companion object {
            @JvmStatic
            @BeforeAll
            fun `start koin`(): Unit {
                startKoin {
                    modules(dbModule)
                }
            }
        }
    }
    

    see these links for more clarification on some methods used

    How to setup Koin in Ktor using testApplication()?

    configuring testing environment for ktor

    a youtrack issue