Testing a Ktor server part I: Route testing

Jorge R.
3 min readJul 31, 2020

--

Image from Tookapic

This is the first of a series of articles about how to test a Ktor server based on the architecture proposal I’ve written before. If you are interested on reading a bit about the different layers in this architecture to correctly distinguish them and their responsibilities, you can have a look here:

What do we need?

First of all, I'm going to explain the different libraries I'm using in the code and why do we need them.

  1. JUnit Jupiter (v5.0): as you may already know, JUnit is a framework to define test suites. This version will help us to avoid mock initialisation per test with TestInstance.Lifecycle.PER_CLASS annotation.
  2. Mockk: it is a nice mocking library for Kotlin that integrates coroutine handling when defining mock functionality.
  3. AssertJ: assertion library with a rich set of assertions and truly helpful error messages.
  4. Ktor server testing library: helpful set of tools to make tests against Ktor server application.

What are we going to test?

This is the key question you should ask yourself before start writing the first line of code. When talking about testing, it is crucial to understand what do you want to test and what is needed in order to isolate the logic we are targeting in our tests. Let's discuss about this with the following diagram:

Ktor routing dependencies

Our target layer is routing and it is defined inside ModuleA and ModuleB. On the left, we have our application and, on the right, each module dependencies. As ModuleA and ModuleB are function extensions that are installed in the application, we are going to use Ktor server testing library to launch a TestApplication that will use our routing modules. To solve dependencies on the right (controllers), we will provide mock objects using Koin injection.

Now that we know which is the scope of our testing, we should define the scenarios we are going to cover. Scenarios are defined by those parameters we input to our subject under test and those return values from its dependencies. Later in this article I will show a successful user creation scenario.

Let me see the code!

Now that we know our dependencies and what is needed in order to run our tests, let's structure it a bit. In order to avoid boilerplate code, I've placed a base function in a BaseRoutingTest class that will install routing modules (moduleList() function invoke) and also dependencies injection with Koin:

To use this function, we would need to initialise in our test classes both koinModules variable with specific module's controller (and other dependencies) and moduleList() function with our routing modules. A test example using this would be:

You can see in the previous example, I've defined the test following the three basic steps: arrange, act and assert. During the arrange step, I've generated a responseUser instance to define what the controller needs to return in this happy case scenario. In the act step, I've made a call to the testApplication with the specific parameters to match the desired route. Finally, in the assert step, I'm checking that the expected response is returned.

With all of this, we are ready to test all routes in our Ktor server.

I will be covering Controller's testing in a next article. If you want to be aware, you can follow my profile @Medium to be notified.

If you want to give it a try, clone the following Github repository and don't be shy to leave a comment or a PR if you feel you can add something. Both are welcome!

Have fun and happy “clean” testing!

Update: I will update Ktor dependencies and I will also merge extra features into master, so some of the explanations in this article may refer to this state of the code:

https://github.com/mathias21/KtorEasy/tree/simpleCompose

--

--