Unit testing is often discussed in software development. When it comes to writing functions, we tend to think about how we can integrate that function in the entry point of the application. When it comes to unit testing that function, having all branch coverage inside that function can be hard and tricky.
Akka HTTP has a helpful testkit to test routes that make unit testing simple. Testing without TCP connection should not be complicated. You can mock the IO function, such as singleRequest
. The key to a readable and accessible test suite lies in how you structure the functions.
In this tutorial, I want to share how you can structure the Http singleRequest
function in Akka HTTP so that it is easier to mock and get 100% branch coverage.
The Problem
In this tutorial, I use a fake response of a simple dummy rest-client which retrieves dummy employee information. The task is to create a client service that gets the employee information through Http SingleRequest and parse all the fields in the response. Usually, we write a function like this:
The function creates a fetch call through http().singleRequest
and deserialize the JSON string to an Employee model class with Circe.
This function becomes hard for unit testing because the IO function coupled with other operations in the function.
The Intuition
To make the application as loosely coupled as possible. We can wrap the IO function into a trait so that the functionality can be overridden or mocked.
In this case, http().singleRequest
is the component that creates IO. Therefore, we can extract the functionality out and wrap it into a trait.
Because we extracted the singleRequest
as an abstract function, we can mock the function in the unit test with Scalamock.
Order of Execution
We can write the regular implementation first; then we can refactor the function by extracting the http().singleRequest
.
We will use Circe to encode and decode JSON String, and Scalamock to mock the IO functions.
Let’s create a domain model Employee:
Once we create a domain model, let’s create the main class. Writing the main class helps you design how you want to write the function. It also helps to understand how another caller uses your function. Therefore, the main class is a simple way to test the function.
Inside this function, we extract out the fetch endpoint URL by wrapping it with the getEmployee
function.
The Refactor
Let’s refactor the code so that handleRequest
can be mock.
How?
Let’s create a trait, HttpClient, that has an abstract function sendRequest
. Then, let’s mock sendRequest
later in the unit test.
Then, let’s use self-type annotation in the EmployeeRestClient
trait so that any instantiation or any class that extends the EmployeeRestClient
also need to extend the HttpClient trait. This is very useful for later when we instantiate the service.
Then, we create ClientHandler
trait which contains the http().singleRequest
Function. We will not test this trait, but this trait will be mocked in the unit test later.
This refactor separated JSON deserialization and singleRequest
. Then, we can test EmployeeRestClient
trait getEmployees
, by mocking the function on sendRequest
.
Testing
Let’s set up all the mocks.
Since we extract httpRequest
, we can create another trait that also extends HttpClient
and mock the function.
You can specify the expected input and returns on the function.
Scalamock automatically mock the function for you. Therefore, no side effect or IO involved in running the getEmployee
.
Once we mocked the function, we start setting up the test suite. The test suite involves a setup environment, mock expectation, and assertion. Once we create an expectation for the function in ScalaMock, it also creates an assertion. If there is a discrepancy between the function call and the expectation, that test suite fails.
Did you notice that the test suite is precisely like the Main function that we defined earlier?
Starting from the Main function makes it easy to construct the test suite.
The difference between the main and the test suite lies in the different ClientHandler
traits. In main, we extend EmployeeRestClient
with ClientHandler
. In the test suite, we mock the ClientHandler
.
By doing this, we can test getEmployee
functionality because we wrap the http().singleRequest
separately into another function.
In Conclusion:
- Design your function from the main class
- Refactor your design by extract the IO portion of the function for mocking later.
- Mock the extracted IO function with any mock library you want, in this case, Scalamock.
- Construct your test suite like how you would call your function from the main class
That’s it! I hope this helps you write better unit test in your Scala projects or unit test HttpClient
request. The full implementation of this tutorial is on GitHub. Please comment below if there are any questions and comments. If you have any other great strategies, feel free to share your knowledge in the comment section below!