Let's suppose that we want to dismiss all calls for api/restricted endpoint if they don't have special header, in this case "X-Special-Header". We access header via get PipelineContext  and check if that value is null or blank. If so, we respond with HTTP 403. If given header is present,  we proceed with some business logic and respond with HTTP 200.

Usually that kind of mechanisms are implemented in application-wide request interceptors, so there is no need to check header presence in controller method

System under test implementation

The very basic implementation looks like that:

import io.ktor.application.*
import io.ktor.application.Application
import io.ktor.http.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*

fun Application.restrictedApiModule() {
    routing {
        get("/api/restricted") {
            val authorizationHeader = context.request.header("X-Special-Header")
            if (authorizationHeader.isNullOrBlank()) {
                call.respond(HttpStatusCode.Forbidden)
            } else {
                //do some business logic
                call.respond(HttpStatusCode.OK)
            }
        }
    }
}
System under test - restricted api module

How we would like to approach writing tests for that?

Test case

Let's start with writing test method declaration:

class RestrictedApiTest : StringSpec({
    "given X-Special-Header header is present when handle request then respond OK"{
        
    }
}

We will make use of Ktor library for testing: ktor-server-test-host (available as Gradle dependency)

testImplementation "io.ktor:ktor-server-test-host:1.4.0"
package io.ktor.server.testing


/**
 * Start test application engine, pass it to [test] function and stop it
 */
fun <R> withTestApplication(moduleFunction: Application.() -> Unit, test: TestApplicationEngine.() -> R): R {
    return withApplication(createTestEnvironment()) {
        moduleFunction(application)
        test()
    }
}

Function withTestApplication accepts function on Application - we can pass our restrictedApiModule in the following way:

class RestrictedApiTest : StringSpec({
    "given X-Special-Header header is present when handle request then respond OK"{
        withTestApplication(moduleFunction = { restrictedApiModule() }) {

        }
    }
})

And make test call handleRequest() method:

class RestrictedApiTest : StringSpec({
    "given X-Special-Header header is present when handle request then respond OK"{
        withTestApplication(moduleFunction = { restrictedApiModule() }) {
            val testCall: TestApplicationCall = handleRequest(method = HttpMethod.Get, uri = "/api/restricted") {
                addHeader("X-Special-Header", "some-header-value")
            }
        }
    }
})

We define HTTP method which should be executed, and additionally add given header. Then, we can perform assertion on TestApplicationCall - in this case check if response status is actually HTTP 200 - OK.

import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import io.ktor.http.*
import io.ktor.server.testing.*

class RestrictedApiTest : StringSpec({
    "given X-Special-Header header is present when handle request then respond OK"{
        withTestApplication(moduleFunction = { restrictedApiModule() }) {
            val testCall: TestApplicationCall = handleRequest(method = HttpMethod.Get, uri = "/api/restricted") {
                addHeader("X-Special-Header", "some-header-value")
            }

            testCall.response.status() shouldBe HttpStatusCode.OK

        }
    }
})
Test case - check if HTTP 200 was returned when X-Special-Header was present in request

More test cases

We also should check other test case - if there was HTTP 403 - Forbidden returned when our header is blank or null.

import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import io.ktor.http.*
import io.ktor.server.testing.*

class RestrictedApiTest : StringSpec({

    "given X-Special-Header header is not present when handle request then respond Forbidden"{
        withTestApplication(moduleFunction = { restrictedApiModule() }) {
            val testCall: TestApplicationCall = handleRequest(method = HttpMethod.Get, uri = "/api/restricted"){
                addHeader("X-Special-Header", "")
            }

            testCall.response.status() shouldBe HttpStatusCode.Forbidden
        }
    }

    "given X-Special-Header header is present when handle request then respond OK"{
        withTestApplication(moduleFunction = { restrictedApiModule() }) {
            val testCall: TestApplicationCall = handleRequest(method = HttpMethod.Get, uri = "/api/restricted") {
                addHeader("X-Special-Header", "some-header-value")
            }

            testCall.response.status() shouldBe HttpStatusCode.OK
        }
    }
})

Test results, as rendered in IntelliJ

Summary

The whole Ktor server environment feels lightweight and flexible - as opposed to Spring (which by the way has Kotlin support).

With tooling like that it’s just doesn’t feel right to omit functional tests in application.

Get new posts directly to your inbox, subscribe to our newsletter

* indicates required

For GDPR compliance, I have to ask you to allow me to send emails to you. I will use your email address to notify you about new content on my site.

You can unsubscribe at any time by clicking the link in the footer of our emails. For information about our privacy practices, please visit our website.

We use Mailchimp as our marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp's privacy practices here.