Dmytro Koval
11/07/2024, 2:05 PMsingle
defined in the main application with a Mockk
in tests. I was expecting Koin to pickup the customerServiceMock
instead of the production CustomerService
, but it doesn’t.
Here’s a sample of the test:
class APITest :
FunSpec(),
KoinTest {
override fun extensions(): List<Extension> = listOf(
WireMockListener(jwksWireMockServer, ListenerMode.PER_SPEC),
KoinExtension(
module = module {},
mockProvider = { mockkClass(it, relaxed = true) },
mode = KoinLifecycleMode.Root,
),
)
init {
val testDb = install(DatabaseTestConfig.jdbcTestExtension).createTestDatabase()
val appConfig =
listOf(
ApplicationConfig("application.test.conf"),
testDb.applicationConfig,
JwksWireMockSetup.appConfig,
).reduce(ApplicationConfig::mergeWith)
test("Empty tables and no defaults should return empty results") {
testApplication {
environment {
config = appConfig
}
val customerServiceMock = declareMock<CustomerService>()
coEvery { customerServiceMock.getCustomer(any()) }.returns(Customer(CustomerId("1"), 30))
val cli = createTestClient()
cli.get("/api/v1/recommendations?customerId=1").apply {
status shouldBe HttpStatusCode.OK
body<Recommendations>() shouldBe Recommendations.empty()
}
}
}
}
}
Dmytro Koval
11/08/2024, 11:57 AMDmytro Koval
11/08/2024, 11:59 AMrouting
is configured in complicated-ish way.
import com.example.routing.testRouting
import io.ktor.server.application.Application
fun Application.configureRouting() {
testRouting()
}
...
import com.example.services.ServiceA
import com.example.services.ServiceB
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.Application
import io.ktor.server.application.call
import io.ktor.server.response.respond
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import io.ktor.server.routing.routing
import kotlinx.serialization.Serializable
import org.koin.ktor.ext.inject
fun Application.testRouting() {
routing {
val serviceA by inject<ServiceA>()
val serviceB by inject<ServiceB>()
test(serviceA, serviceB)
}
}
@Serializable
data class Response(val a: String, val b: String)
fun Route.test(serviceA: ServiceA, serviceB: ServiceB) {
get("/test") {
call.respond(status = HttpStatusCode.OK, Response(serviceA.identify(), serviceB.identify()))
}
}
Dmytro Koval
11/08/2024, 12:00 PMrouting {}
in configureRouting()
the mocks are injected correctly.arnaud.giuliani
11/15/2024, 1:27 PMLeoColman
11/15/2024, 2:41 PMNote: If you have the application. conf file in the resources folder, testApplication loads all modules and properties specified in the configuration file automatically.
Your application.conf
is defined as
ktor {
deployment {
port = 8080
}
application {
modules = [com.example.ApplicationKt.module]
}
}
and com.example.ApplicationKt.module
ends up calling
fun Application.configureKoin() = install(Koin) {
slf4jLogger()
val config = module {
single<ServiceA> { ServiceA() }
single<ServiceB> { ServiceB() }
}
modules(config)
}
So by the time your test starts, Koin is already ready to go with an actual instance of ServiceB
and it's no longer possible to replace it with another implementation by using the regular declareMock
.
My guess for your work around is that you skipped the testApplication
bit that sets Koin up.
I'm not an ace in Ktor, so I might have missed a point