Unit testing with JUnit5 and Mockk for Java developers who are new to Kotlin (just like me!)
Before we begin
An initiative that started with one of our SDEs in the team, to gradually introduce Kotlin as a server-side development language was adopted by other team members.
We already had our “old habits” and our “development patterns” from other programming languages, and now we are looking into finding patterns and habits with Kotlin. By no means I am an experienced Kotlin developer, so please read the following post with a grain of salt.
Mission statement
As we are gradually moving to Kotlin, we would like to use existing tools and frameworks currently in use where possible.
For the following example, I will assume a team is using Mockito for unit tests and Junit5.
The team is gradually moving to Kotlin and would like to continue using these tools if possible.
The domain
I will use as an example a book library management system (BLMS) that uses an RPC client that was developed in a JVM based language, which is not Kotlin (Java in our case).
The client will be consumed by the LBMS code, that is developed using Kotlin.
The RPC client has CRUD methods for items management. Each item has name, description, unique identifier and parent unique identifier.
The BLMS code will have two main domain model classes: A library and a book.
The java part
Let’s go over the provided Items manager RPC client and its related classes:
An item is composed of a unique identifier, a name, and a description:
The customers are provided with an interface to use to make the RPC calls:
Beginning of implementation of BLMS using Kotlin
First, let's create the data classes for book and library:
A book will have a unique identifier, a name, and a genre.
A library will have a unique identifier, a name, and an address.
As Book and Library are classic model classes which represent data, we will use Kotlin’s data classes:
Creating mapper functions
We can use Kotlin’s extensions functions to create mappers from Item to Book and Library and vice versa. As we can see in the example, the description field of the Item class is used to store either the genre of the book or the address of the library
Time for the BLMS proxy code
The code utilizes the RPC client.
A few notes here:
a. Notice how the items manager is passed via the primary constructor, and that the code does not include an explicit definition of a field of items manager, as this is handled by definition of the primary constructor
b. Java Optionals are mapped to Kotlin’s nullable types and a default null value is being used.
c. We need to write explicit code to convert from Kotlin’s nullable to Java’s optional. I hope in the future Kotlin will introduce a feature that will save this code.
Testing: from Mokcito to Mockk
At first, I realized I can provide more descriptive test function names, as I can use function names that include spaces:
The second thing I noticed that Mockio’s “when” static method which is used to define the behavior of the mocked method is a Kotlin keyword, which is used for conditional flows.
It is possible to use the “when” static method, in the following way:
And yet, it did not feel natural.
At this point, I decided to look into “Kotlin Mockito” (see here), which introduces the “whenever” function:
By default classes and functions are “final” in Kotlin.
Mockito 1.x does not allow to mock final classes and methods, but with Mockito 2.x (and kotlin-mockito 2.x) it is possible, so there is no need to define the classes and the functions as “open”.
At this point I felt that I can write Mokcito based tests, but still I preferred to explore a different mocking library, called Mockk (see here), as its syntax was more appealing:
a. Instead of using “doNothing().when(…)” syntax, the “just Runs” syntax is used.
b. The usage of a lambda to provide mocked scope, and the ability to use the curly brackets syntax and provide the lambda outside of the arguments list made the call to “every” (which replaces “when”) to be more readable.
c. The usage of named arguments with “verify” allows to explicitly provide an integer value to the “exactly” argument, replacing “times(n)” and “never()” in Mockito.
At first, my test was failing.
Kotlin’s data classes have behind the scene implementation for “hashCode” and “equals”. I fixed my Item class (which was coded in Java), added “equals” method and the test started passing.
Testing: parametrized testing
At this point, I wanted to use parameterized data sets for my tests.
Junit5 allows to use static methods as data providers for parametrized testing, see the below Java code snippet:
Although we can use Kotlin’s companion objects to call functions without instantiating a class, I think this is overhead. One of our SDEs suggested I use TestFactory instead, a cool feature of JUnit5.
In a nutshell, test functions (or methods, in Java) that are annotated with “@TestFactory” should return a collection of DynamicTest objects.
DynamicTest objects are created using the static factory method “dynamicTest” which receives a description and an executable which is a SAM (single abstract method) interface.
Using the “listOf” top-level function, and the fact Kotlin’s collections have functions which reminds Java’s stream API, I could create parameterized tests quite easily.
At first, I created a data classes to represent the required data for a test case:
Then, as you will see in the code, I created two helper functions to create paginated lists of Item objects and of Book objects:
I then created a few private fields to be used as my test data:
Then, using Kotlin’s extension functions feature, I extended ListBooksTestCase, and create a function to return a DynamiTest object from the test data:
And eventually created the test factory function to use it all:
(You can see the whole test file by clicking here)
Conclusion and what’s next
In this post, I demonstrated an example of how we started integrating Kotlin to our codebase.
The example covered developing new feature using Kotlin and interacting with a Java client of existing service, as well as (partial) unit testing of the new code.
The post did not cover a scenario in which other Java code based (perhaps developed by other teams) will interact with code developed using Kotlin.
Although for the latter scenario, this is quite straightforward, there are still some caveats I will handle in a future post.