Ktor / Kodein - How to write Integration Tests - kotlin

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?

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

Related

Ktor Resources validation plugin

I've a Ktor server application using the Resources plugin for type-safe routing. Now I want to create a custom plugin to validate the resource instance.
But I can't figure what is the correct phase for intercepting the pipeline.
A custom "Validation Phase" inserted before the call phase seems to be executed to early as the Resource instance is not yet added to the ApplicationCall attributes. I do not really understand why thats the case, because the decoding of the Resource instance should by done in the Plugins phase? Found the following in io.ktor.server.resources.Routing:
public fun <T : Any> Route.handle(
serializer: KSerializer<T>,
body: suspend PipelineContext<Unit, ApplicationCall>.(T) -> Unit
) {
intercept(ApplicationCallPipeline.Plugins) {
val resources = application.plugin(Resources)
try {
val resource = resources.resourcesFormat.decodeFromParameters(serializer, call.parameters)
call.attributes.put(ResourceInstanceKey, resource)
} catch (cause: Throwable) {
throw BadRequestException("Can't transform call to resource", cause)
}
}
...
}
If I add my custom validation phase after the call phase it's executed to late, after the route handler.
Here some example code...
Route and Resource:
fun Route.exampleRouting() {
get<ExampleResource> { example ->
println("Validated value: ${example.somevalue}")
call.respond(HttpStatusCode.OK)
}
}
fun Application.registerExampleRoutes() {
routing {
exampleRouting()
}
}
#Serializable
#Resource("/example")
class ExampleResource(val somevalue: String)
Custom validation plugin:
val ResourcesValidation = createApplicationPlugin("ResourcesValidation") {
on(ValidationHook) { call ->
val resourceInstanceKey =
call.attributes.allKeys.filterIsInstance<AttributeKey<Any>>().find { it.name == "ResourceInstance" }
// PROBLEM: resourceInstanceKey is null here, ResourceInstance not yet added to call attributes
resourceInstanceKey?.let {
val resourceInstance = call.attributes[resourceInstanceKey]
// Validate resource instance here...
println("validated")
}
}
}
object ValidationHook : Hook<suspend (ApplicationCall) -> Unit> {
val ValidationPhase: PipelinePhase = PipelinePhase("Validation")
override fun install(
pipeline: ApplicationCallPipeline,
handler: suspend (ApplicationCall) -> Unit
) {
pipeline.insertPhaseBefore(ApplicationCallPipeline.Call, ValidationPhase)
pipeline.intercept(ValidationPhase) { handler(call) }
}
}
And of cause installing the plugin and registering the routes in the Application:
fun Application.module() {
...
install(ResourcesValidation)
...
registerExampleRoutes()
...
}
I've tried the same with the Base API but same result.
So..is there any way to intercept the pipeline at the right time to validate the Resource instance before the route handler is executed?
To solve your problem you can write a RouteScopedPlugin and install it into the routing because a resource instance is put into the call attributes while interception of a route's call pipeline, not an application's pipeline.
fun main() {
embeddedServer(Netty, port = 4444) {
install(Resources)
routing {
install(ResourcesValidation)
get<ExampleResource> { example ->
println("Validated value: ${example.somevalue}")
call.respond(HttpStatusCode.OK)
}
}
}.start(wait = true)
}
val ResourcesValidation = createRouteScopedPlugin("ResourcesValidation") {
on(ValidationHook) { call ->
try {
val resourceInstance = call.attributes[AttributeKey("ResourceInstance")]
println("validated")
} catch (_: IllegalStateException) {
// attribute not found
}
}
}
object ValidationHook : Hook<suspend (ApplicationCall) -> Unit> {
val ValidationPhase: PipelinePhase = PipelinePhase("Validation")
override fun install(
pipeline: ApplicationCallPipeline,
handler: suspend (ApplicationCall) -> Unit
) {
pipeline.insertPhaseAfter(ApplicationCallPipeline.Plugins, ValidationPhase)
pipeline.intercept(ValidationPhase) { handler(call) }
}
}

Test case is calling actual method even after mocking the method call in ktor framework (kotlin)

I am testing an API written in Kotlin using the KTOR framework. For the testing, I am using JUnit5 and Mockito. There is a route class where a route is defined which I need to test. Here is the route class :-
fun Application.configureRouting() {
routing {
post("/someRoute") {
val service = MyService()
val request: JsonNode = call.receive()
launch {
service.dummyFunction(request)
}
val mapper = ObjectMapper()
val responseStr = "{\"status\":\"success\",\"message\":\"Request has been received successfully\"}"
val response: JsonNode = mapper.readTree(responseStr)
call.fireHttpResponse(HttpStatusCode.OK, response)
}
}
}
This is the test case I am writing for it :-
class RouteTest {
#Mock
var service = MyService()
// read the configuration properties
private val testEnv = createTestEnvironment {
config = HoconApplicationConfig(ConfigFactory.load("application.conf"))
}
#Before
fun setUp() = withApplication(testEnv) {
MockitoAnnotations.openMocks(MyService::class)
}
#Test
fun test() = withApplication(testEnv) {
withTestApplication(Application::configureRouting) {
runBlocking {
Mockito.`when`(service.dummyFunction(Mockito.any()).thenReturn(true)
with(handleRequest(HttpMethod.Post, "/someRoute") {
setBody("some body")
}) {
assertEquals(HttpStatusCode.OK, response.status())
}
}
}
}
}
When I run the test, it calls the actual "dummyFunction()" method instead of the mocked one and hence, it is failing. Am I doing something wrong?
Because your service in test is different from the service you mocked. To solve this, you need to inject the service into your class, or pass the service as an argument.
Read more: IoC, DI.
The simplest way to solve your problem is to define the service parameter for the configureRouting method and pass a corresponding argument in the test and production code when calling it.
fun Application.configureRouting(service: MyService) {
routing {
post("/someRoute") {
val request: JsonNode = call.receive()
launch {
service.dummyFunction(request)
}
val mapper = ObjectMapper()
val responseStr = "{\"status\":\"success\",\"message\":\"Request has been received successfully\"}"
val response: JsonNode = mapper.readTree(responseStr)
call.fireHttpResponse(HttpStatusCode.OK, response)
}
}
}
class RouteTest {
#Mock
var service = MyService()
private val testEnv = createTestEnvironment {
config = HoconApplicationConfig(ConfigFactory.load("application.conf"))
}
#Test
fun test() = withApplication(testEnv) {
withTestApplication({ configureRouting(service) }) {
runBlocking {
// Your test...
}
}

startKoin in KoinTest-class throws "A KoinContext is already started"

I'm using "withTestAppliction" in one of my tests to test if the route works. Before all Tests the DB-Table "cats" should have no entries. To get the DAO I need Koin in this Test but if conflicts with "withTestAppliction" where Koin will also be startet and throws A KoinContext is already started
[Update]
I know I could use something like handleRequest(HttpMethod.Delete, "/cats") but I don't want to expose this Rest-Interface. Not even for testing.
#ExperimentalCoroutinesApi
class CatsTest: KoinTest {
companion object {
#BeforeClass
#JvmStatic fun setup() {
// once per run
startKoin {
modules(appModule)
}
}
#AfterClass
#JvmStatic fun teardown() {
// clean up after this class, leave nothing dirty behind
stopKoin()
}
}
#Before
fun setupTest() = runBlockingTest {
val dao = inject<CatDAO>()
dao.value.deleteAll()
}
#After
fun cleanUp() {
}
#Test
fun testCreateCat() {
withTestApplication({ module(testing = true) }) {
val call = createCat(predictName("Pepples"), 22)
call.response.status().`should be`(HttpStatusCode.Created)
}
}
}
fun TestApplicationEngine.createCat(name: String, age: Int): TestApplicationCall {
return handleRequest(HttpMethod.Post, "/cats") {
addHeader(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded.toString())
setBody(listOf(
"name" to name,
"age" to age.toString()
).formUrlEncode())
}
}
After test (after withTestApplication()) call KoinContextHandler.get().stopKoin().
Example: https://github.com/comm1x/ktor-boot/blob/master/test/common/common.kt
It looks similar to the issue I faced. The problem was that the module() passed under the withTestApplication() was trying to create the Koin object again. I replaced the module() with specific modules that I had to load for the tests except for the Koin.
Refer - test sample and
application sample
Had the same problem executing multiple tests in a class. After removing the init/startKoin (since it's initialized in the Application when you test with the emulator).
I am not really sure if this is the correct approach, but it kind of works for me and my build server.
#ExperimentalCoroutinesApi
#RunWith(AndroidJUnit4ClassRunner::class) // or JUnit4..
class MyTest : KoinTest {
private val mockedAppModule: Module = module(override = true)
factory { myRepo }
}
#Before
fun setup() {
loadKoinModules(mockedAppModule)
}
#After
fun tearDown() {
unloadKoinModules(mockedAppModule)
}
#Test
fun testSubscriberRegistration() = runBlockingTest { // only needed if you are using supend functions
// test impl...
}
}

Shutting down / Closing Kodein context

Say in my application I maintain a few Kodein contexts and there are some shared resources in the Kodein contexts which I would like to close when the context it belongs to is no longer needed.
Below is a simple illustration of the problem:
class SomeConnectionPool: Closeable {
override fun close() { /* some operation */ }
}
class SomeResource: Closeable {
override fun close() { /* some operation */ }
}
class SomeService(val pool: SomeConnectionPool) {
fun doStuff() { /* some operation */ }
}
class SomeOtherService(val pool: SomeConnectionPool) {
fun doOtherStuff() { /* some operation */ }
}
val kodein = Kodein {
bind<SomeConnectionPool>() with singleton { SomeConnectionPool() }
bind<SomeResource>() with singleton { SomeResource() }
bind<SomeService>() with singleton { SomeService(instance()) }
bind<SomeOtherService>() with singleton { SomeOtherService(instance()) }
}
fun main(args: Array<String>) {
val service: SomeService by kodein.instance()
service.doStuff()
// this will initialize everything even the unused bindings
val resources by kodein.allInstances<Closeable>()
resources.forEach { it.close() }
}
Ideally there are several properties that should be achieved:
Only retrieve the Closeable instances that are already initialized and close them
SomeService and SomeOtherService should not be responsible of closing SomeConnectionPool since they did not create the instance. They also do not know whether the pool is still being used by something else.
I also considered to retrieve only initialized bindings from kodein.container, but there seems to be no apparent way to do so.
Kodein 5.1 has you covered.
Have a look at:
http://kodein.org/Kodein-DI/?5.1/core#scope-closeable
http://kodein.org/Kodein-DI/?5.1/android#_android_scopes

Configurable factory in Kotlin for JavaScript

I have a Kolin class A whose property is either supplied in a primary constructor, or is created by a factory in the secondary constructor of A.
interface I
class O : I
class A (val i: I) {
constructor(): this(factory!!.create())
}
interface Factory {
fun create(): I
}
class MyFactory: Factory {
override fun create(): I {
return O()
}
}
var factory: Factory? = null
fun main(args: Array<String>) {
factory = MyFactory()
A()
}
When I compile this code to JavaScript (Kolin Compiler version 1.0.6-release-127) and run it in a browser (Safari 10.0.3), I get the following runtime error:
ReferenceError:Can't find variable: tmp$0
The error occurs in the secondary constructor of A. It seems that Kolin has a problem in performing the null check of a parameter in the secondary constructor. The code runs correctly when I change the factory declaration to "not null" and remove the factory initialization from the main() method:
val factory: Factory = MyFactory()
fun main(args: Array<String>) {
A()
}
But this is not what I want, since I want to be able to configure the factory at application startup time.
Do I miss something, or is this a bug in Kotlin's JavaScript compiler? Does anybody know a workaround for this problem? Is there another or better way to design configurable factories in Kotlin for JavaScript?
Generated JavaScript code:
var KotlinTest = function (Kotlin) {
'use strict';
var _ = Kotlin.defineRootPackage(function () {
this.factory = null;
}, /** #lends _ */ {
I: Kotlin.createTrait(null),
O: Kotlin.createClass(function () {
return [_.I];
}, function O() {
}),
A: Kotlin.createClass(null, function A(i) {
this.i = i;
}),
A_init: function ($this) {
$this = $this || Object.create(_.A.prototype);
_.A.call($this, ((tmp$0 = _.factory) != null ? tmp$0 : Kotlin.throwNPE()).create());
return $this;
},
Factory: Kotlin.createTrait(null),
MyFactory: Kotlin.createClass(function () {
return [_.Factory];
}, function MyFactory() {
}, /** #lends _.MyFactory.prototype */ {
create: function () {
return new _.O();
}
}),
main_kand9s$: function (args) {
_.factory = new _.MyFactory();
_.A_init();
}
});
Kotlin.defineModule('KotlinTest', _);
_.main_kand9s$([]);
return _;
}(kotlin);
Answering my own question. As #marstran correctly commented, this seems to be a bug in Kolin's Javascript compiler 1.0.6 which seems to be fixed in 1.1-beta. In the meantime, I'm using a workaround similar to the following:
interface I
class O : I
class A (val i: I) {
constructor(): this(Factory.get().create())
}
interface Factory {
companion object {
private var IMPL : Factory? = null
fun get(): Factory = IMPL!!
fun set(impl: Factory) {
IMPL = impl
}
}
fun create(): I
}
class MyFactory: Factory {
override fun create(): I {
return O()
}
}
fun main(args: Array<String>) {
Factory.set(MyFactory())
A()
}