Testing a Ktor server part II: Business layer

Jorge R.
4 min readAug 3, 2020

--

Image by Pixabay

In my last article I've explained how to test route definition using a proposed architecture in a Ktor server, and also the tools you would need for that. Those tools are going to be used in this part as well, so if you want to check it before continuing with this article, just follow the link below:

What are we going to test?

In this case, our testing target will be our controllers. This layer is responsible of server business logic and also to manage database transaction blocks (dbQuery function), so I will dedicate a bit more time explaining how should we test this.

Controller's dependencies

As we can see in the image above, controllers dependencies will be API's injected objects (and maybe some other providers that we can use to satisfy our business logic needs). As mentioned in the previous article, we need to disconnect from dependencies by injecting mocks. API objects are implementing an interface, so we will use those when defining our mock's type. By using Mockk we could directly use our implementation type, but it is a best practice to disconnect dependencies by interfaces, so let's do it in the right way.

Controller's test instrumentation

Before going into test instrumentation, let's recap about how a controller's code looks like:

We've already discussed how are we going to approach API's dependencies, but we still need to instrument database transaction blocks. Database transactions are executed by dbQuery() functions, receiving a lambda that will be run in a transactional context. It is important to handle transactions in this layer as it will allow us to decide when do we want to start them and how many of our previous queries will be rolled back in case of an error. Let's see how database transaction blocks are implemented in BaseController class:

As you can see in the previous snippet, I'm allowing database queries in controllers by placing dbQuery function in the BaseController class, and to follow a clean architecture I have to inject a database provider interface there. This will seal the deal on database queries instrumentation by simply mocking that database provider interface. This is placed in a BaseControllerTest class, so I can avoid repeating it in every Controller's test class.

The last thing missing here is to define database provider mock's behaviour when calling dbQuery function. In a testing scope, we just want to get the lambda function to be executed, so we invoke the first argument (line 12). This is a common problem to solve in testing when passing lambda functions as parameters, as lambda code is part of the subject under test.

Testing a controller

All the needed instrumentation is defined to start testing controllers, so let's see a test example:

Now, to create a controller’s test, I have to initialise injection with the dependencies modules, call super.before() function before each test and clear other mocks. Once this is done, I just need to follow the standard Arrange → Act → Assert structure to define my tests.

One last note here, in order to use dbQuery suspend function, controller's functions are suspending functions as well, so when "acting" in tests, it is needed to use runBlocking {}.

I hope you have learnt something while reading this article. If you want to check the code, you can find it in Github!

Next article, I will be talking about how to test API layer, so press follow to be notified once I publish it.

If you feel like I'm missing something or I've made a mistake somewhere, write me a comment or make a PR. I will be very happy to get feedback!

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

--

--