Over a million developers have joined DZone.

Writing Test Data Builders Made Easy With Kotlin

In this article I will be focusing on a few features of Kotlin that help in writing concise and readable code. To explain the features, I will be using the concept of Test Data Builders.

· Java Zone

Discover how powerful static code analysis and ergonomic design make development not only productive but also an enjoyable experience, brought to you in partnership with JetBrains

In this article I will be focusing on a few features of Kotlin that help in writing concise and readable code. To explain the features, I will be using the concept of Test Data Builders, as described in the book Growing Object-Oriented Software, Guided by Tests. I hope that by the end of this article I will have generated enough interest in you to try out Kotlin. If you are not aware, Kotlin, developed by JetBrains, is a statically typed programming language for the JVM. It is fully interoperable with Java (i.e. Kotlin code can be called from Java and vice versa).

I will be focusing on the following features of Kotlin:

The Concept of Test Data Builders

When you write a test, unit, or integration, you need test data. A domain object may exist in many different states, depending on how it is constructed and modified. These different states of an object generally from the test data. The code for construction and modification of objects has to be written in every test which uses the object. When the object is complex, the code to construct and modify it becomes complex thus reducing the readability and maintainability of the tests.

By using Test Data Builders, which uses Builder Pattern, the construction and modification of a complex object can be made simple. We will see how the usage of builders leads to better readability and maintainability and see how Kotlin makes it easy to write these builders.

Example Object

Before we look at one of these builders, let's see an example of an object for which a test data builder may be useful. This example has been taken from the documentation of make-it-easy, a tiny framework that makes it easy to write the Test Data Builders in Java.

class Apple(private val leaves: Int) {
    private var ripeness = 0.0

    fun ripen(amount: Double) {
        ripeness = Math.min(1.0, ripeness + amount)

    fun isRipe() = ripeness == 1.0

    fun numberOfLeaves() = leaves

From the code above the following observations can be made:

  • The object in this case in an Apple
  • Number of leaves is defined using a constructor in the first line. Notice how concise the constructor definition is.
  • By default an apple is unripe (i.e. ripeness = 0.0).
  • Ripeness can be modified using the ripen() method.

Examples of test data involving apple can be:

  • An unripe apple with 2 leaves (leaves = 2 and ripeness = 0.0),
  • ripe apple with 1 leaf (leaves = 1 and ripeness = 1.0), and many more.

Test Data Builder for Apple

Let's define a Builder for the Apple now.

data class AppleBuilder(val ripeness: Double = 0.0, val leaves: Int = 1) {
    fun build(): Apple {
        return Apple(leaves).apply { ripen(ripeness) }

From the code above the following observations can be made:

  • The builder defines two properties, ripeness and leaves, in its constructor and both of them have default values.
  • It uses the leaves property to construct an apple by making the call Apple(leaves).
  • It uses the ripeness property to set the appropriate ripeness on the constructed apple using apply { ripen(ripeness) }, which is an extension function. I am not covering extension functions in this article. apply is simply calling the { ripen(ripeness) } function on newly created apple.
  • The data keyword in the first line defines the AppleBuilder as a Data Class. I will be explaining it later in the article.

The AppleBuilder can be created in the following ways to construct instances of apples in tests.

Using Default Arguments

val unripeAppleWith1Leaf = AppleBuilder().build()

In the code above, the AppleBuilder is created by not specifying any constructor arguments. Note that this is possible because the arguments in the constructor definition have default values. The instance of builder takes the default values of ripeness and leaves defined on its constructor and thus builds an unripe apple with one leaf. This feature is called Default Arguments in Kotlin. Using the default values in the arguments of functions and constructor definitions obviates the need of specifying the arguments while making the constructor or function calls. The defined default values are assumed when arguments are not specified.

Using Named Arguments

val unripeAppleWith2Leaves = AppleBuilder(leaves = 2).build()

In this case, the AppleBuilder instance is created with the customized value of leaves i.e. 2 and the default value of ripeness i.e. 0.0 (an unripe apple with 2 leaves). Note that only one of two constructor arguments is specified in the constructor call. This feature is called Named Arguments in Kotlin. Using the Named Arguments while making function or constructor calls obviates the need of specifying all the arguments defined in the constructor or function definition.

Default Arguments and Named Arguments features of Kotlin help in writing concise code by removing the need overloaded functions and constructors.

One more benefit of Named Arguments is that the code becomes more clear on the client/calling side. Compare AppleBuilder(0.5, 2) with AppleBuilder(ripeness = 0.5, leaves = 2). The latter is definitely more readable. In the former you will have to go to the constructor definition to check what properties 0.5 and 2 and correspond to. Also AppleBuilder(ripeness = 0.5, leaves = 2) is same asAppleBuilder(leaves = 2, ripeness = 0.5)Using the Named Arguments while making function or constructor calls the arguments can be defined in any order irrespective of the order defined in the constructor or function definition.

Named Arguments feature of Kotlin helps in making the code readable.

The features provided by Default Arguments and Named Arguments is achieved in Java using overloaded constructors and methods, which increases a lot of boilerplate on the definition side. Also on the usage side the arguments have to be passed in specific order which makes the code prone to errors (in cases when more than one argument of same type is present, there is a risk on the values getting interchanged).

Using Copies of Pre-defined Builders

Suppose many tests use ripe apples as a common condition in their test data but the number of leaves are different in each test. Defining a builder, which can be re-used by the tests, will be helpful. Each test can then create a copy of this generic builder and specify the test specific parameters, which in this case will be leaves, and build apples out of them.

val ripeAppleBuilder = AppleBuilder(ripeness = 1, leaves = 0)

val ripeAppleWith2Leaves = ripeAppleBuilder.copy(leaves = 2).build()

In the above code, ripeAppleBuilder is the reusable builder which can be used by many tests. Though in this case it seems an overhead to create a reusable builder, it becomes beneficial when builders are complex. The copy() method is provided by the Data Classes. If you look at the definition of AppleBuilder again, it has data class AppleBuilder instead of just class AppleBuilder. The copy() method provides a copy of the reusable builder but with 2 leaves instead of 0. All the other properties remain the same as original builder. Kotlin automatically also generates other functions like equals()/hashcode() and toString()using the properties of data classes.

Data Classes in Kotlin also help in writing concise code by generating few useful functions.


Default ArgumentsNamed Arguments and Data Classes are some of many features that Kotlin provide which can be used to write concise and readable code. Kotlin has a potential to be a good addition to your development toolbox.

Learn more about Kotlin, a new programming language designed to solve problems that software developers face every day brought to you in partnership with JetBrains.

kotlin,test data,unit testing,builder pattern,readability,maintainability,jvm,java

Published at DZone with permission of Praveer Gupta. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}