Note: I'm a Kotlin beginner
Developing stack
- Ktor
- Koin
- KMongo: MongoDB
I defined my application to be like this
Route -> Service -> Repository
- Route is define all HTTP request endpoints.
- Service is business logic of application.
- Repository is data access layer for query/persist data to MongoDB.
What I did to test a route, if the simple route like.
fun Route.healthCheck() {
get("/health") {
call.respond(HealthCheckResponse(message = "OK"))
}
}
@Test
fun testHealth() = testApplication {
application {
configureRouting()
}
client.get("/health").apply {
expect {
that(status).isEqualTo(HttpStatusCode.OK)
that(contentType()?.contentType).isNotNull().and {
contains(ContentType.Application.Json.contentType)
}
that(Json.decodeFromString(HealthCheckResponse.serializer(), bodyAsText()).message)
.isEqualTo("OK")
}
}
}
Test above will run good.
But when the case of the route that has DI, injecting CoroutineDatabase
object into Repository
then that repository inject it to Service
and the service inject into Route
.
In unit test code, I defined like below.
// Route define
fun Application.configureRouting() {
routing {
user()
}
}
fun Route.user() {
val userService: UserService by inject()
...
}
class UserServiceImpl(
private val userRepository: UserRepository // <- Repository is injected with MongoDB `CoroutineDatabase` object.
) : AccountService {
...
}
===============
// Unit Test
class UserEndpointTest: KoinTest {
@get:Rule
val koinTestRule = KoinTestRule.create {
modules(module{ single { accountService } })
}
@Test
fun testUserEndpoint() = testApplication {
application {
configureRouting() // -> collecting all extended function of `Route`
}
val client = createClient {
install(ContentNegotiation) {
json()
}
}
val testerEmail = "i_am_testing@the-email.dev"
val requestJson = """
{
"email": "$testerEmail",
"password": "aBCdEf9"
}
""".trimIndent()
val testBody = jsonMapper.decodeFromString(CreateAccountRequest.serializer(), requestJson)
coEvery { mockAccountService.submitUser(any()) } returns User(id = newId(), email = testerEmail, password = "")
client.post("$accountEndpoint/user") {
contentType(ContentType.Application.Json)
setBody(testBody)
}.apply {
expect {
that(status).isEqualTo(HttpStatusCode.Created)
that(contentType()?.contentType).isNotNull().and {
contains(ContentType.Application.Json.contentType)
}
that((body() as AccountResponse).id).isNotBlank()
that((body() as AccountResponse).email).isNotBlank().and {
isEqualTo(testerEmail)
}
}
}
}}
I expected I did mocking service
and it should be inject into Route
then it won't chaining call to repository
and CoroutineDatabase
object.
But when I run it keep connecting to database, I noticed with log below.
2022-09-07 02:51:30.093 [cluster-ClusterId{value='631788a0b273ac122eaa8350', description='null'}-localhost:27017] DEBUG org.mongodb.driver.cluster - Updating cluster description to {type=UNKNOWN, servers=[{address=localhost:27017, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused}}]
And I tried to find out a tutorial to find how to write Unit Test with scenario like these, but I found most of answers on searched results are before Ktor version up. (Noticed from their codes still using withTestApplication {}
which is deprecated already in version 2.1.0
Who can explain the step of Koin DI work and how to interrupt it with mock.
Thanks.