Ktor: Crafting a Stock Portfolio Endpoint – Part 3
Continuing from our detour in Part 2.5, we now turn our focus to today's topic: Unit Testing. This post is part of a series aiming to build a single endpoint with Ktor. Specifically, we're wrapping the AlphaVantage Quote Endpoint. I highly recommend at least skimming through the previous posts if you haven't read them yet, as it will provide useful context. Here are the links to those previous posts: Part 1 (setup & introduction), Part 2 (external endpoint call), Part 2.5 (dependency injection). Having said that, if you're already caught up with the series, let's unit-test our server.
Preparation
For demonstration purposes, we're adding some logic to our endpoint to make it more testable. Currently, the server echoes the AlphaVantage Endpoint's output, as shown below.
In this section, we'll have the AlphaVantageApi.getQuote function simplify the JSON structure and only retain the key fields: price, symbol, change, and changePercent. The streamlined response appears as follows.
So, we'll introduce SimpleStockQuote, a new model mirroring this simplified response.
Response Simplification
The logic to convert from AlphaVantageQuote to SimpleStockQuote is straightforward and can be done inside the AlphaVantageApiImpl.kt. However, as we add more AlphaVantage endpoints or advanced transformations in the future, the class could become unwieldy. Such expansion can lead to significant clutter and complexity. The scalable approach is to create a mapper class that maps to SimpleStockQuote, which makes it a lot more maintainable and testable.
The SimpleStockQuoteMapper is also created using the Bridge design pattern similar to how we created the AlphaVantageApi in Part 2. The design pattern enables the class to be injected easily and tested separately. Now, we can update the AlphaVantageApi to return our new object, SimpleStockQuote.
As you can see, the code in AlphaVantageApiImpl is clean and readable, thanks to the creation of SimpleStockQuoteMapper, but there is still a problem. Remember how AlphaVantageApiImpl was injected in Part 2.5? It does not know how to instantiate simpleStockQuoteMapper yet. There are 2 ways to configure the applicationModule to achieve that.
There are 2 reasons that Option 2 is preferable. First, if we need to inject SimpleStockQuoteMapper into some other classes in the future, we don't have to configure that again. Second, the SimpleStockQuoteMapper is provided as a singleton, which means it doesn't require extra resources to instantiate another mapper.
This strategic setup of SimpleStockQuoteMapper aligns perfectly with our next focus: diving into the intricacies of Unit Testing. With our preparations complete, let's explore how to effectively test our server's functionality to ensure reliability and robustness.
Unit Testing
First, we need to add the dependencies for the testing infrastructure as usual.
Let's write our first test for the application. The code below contains comments explaining the reasons behind each code block.
Despite the accomplishment of writing our first test, we encountered an issue: it doesn't work yet. The error says A Koin Application has already been started. This is because our server already startKoin automatically when we call install(Koin) in configureKoin. Thus, in the test, it runs one more time when we explicitly call startKoin in the setUp function. One workaround is to avoid running configureKoin in a test. Hence, we need to set that up in the Application.kt by adding isProduction flag to determine the environment.
The isProduction flag in Application.module allows us to control the application's behavior depending on the environment. Setting it to false in tests bypasses production configurations (e.g. configuring Koin), which can interfere with our test setup.
Congratulations! You have officially built a server with unit tests. The test above is the most complicated test we can have for our server. Following this, you can write another test when the server fails to get the apiKey and one for the SimpleStockQuoteMapper. For further exploration and to see how these principles are applied, feel free to check out my GitHub repo after attempting to write those on your own.
Conclusion
Building a side project with good design patterns and unit tests can distinguish you from other interns applying for the same job. These skills are highly valued in the industry. I hope this post has demonstrated how easy it is to unit-test a Ktor project. Don’t forget to share your thoughts or questions below, and make sure to subscribe to our newsletter for Part 4 (member-only), in which our server has an actual endpoint instead of printing the response to the browser!
Comments ()