Kotlin Data class’ Copy function - Usage and Application

Manoj Kumar
3 min readJan 19, 2021

The automatically generated equals/hashCode, toString() are the highlights of the Kotlin data class and we are already using that by now. But still there is one another function called Copy() which is used to replicate an object with only few properties changed. Until recently I had never come across the need for it but always wanted to try it out. I have to admit that in addition to its actual functionality there are few advantages that I have enjoyed using it and happy to share.

A Scenario where the vanilla approach does not work:

We for sure have come across a scenario where we have used Parameterized Tests in our projects. These tests usually involves repeating the same test scenario for different parameters i.e, different data. Well, there are no complaints as long as we are using parameters of primitive data types and arrays.

But once the data that we have to test it out becomes complex in structure and also when needed to be tested with combinations of data with one or more parameter changing while others being constant, We will have to constantly keep track of the permutations and combinations of the test data that we would have written until now and finally we loose readability of the code as well.

Exactly the scenarios after writing all those test datas. Not readable!!
Photo by mostafa meraji on Unsplash

Scenario in Detail

For example Let’s say we have to test a scenario with three parameters where one is an JSONObject and the others are Int and String respectively.

The actual scenario is to test a function given all the three params as input and it uses the id param or fallback to email if id is invalid and similarly fallback to otherIds if the email is invalid.

Now, the data that we are going to feed to the above scenario are going to be a large set as we will have to try out valid/invalid cases, combinations of those with all three params and verify the fallbacks.

The Vanilla Approach

@RunWith(Parameterized::class)
class IdTest(private val id: Any?, private val email: Any?, private val otherIds:Any?) {

companion object {
val validId = Ids(1111111111, "abc@example.com", JSONObject().put("id1", "value1"))
@JvmStatic
@Parameterized.Parameters(name = "{index}: {0},{1},{2}")
fun createTestData() = arrayOf(
arrayOf(1111111111, "abc@example.com", JSONObject().put("id1", "value1")),
arrayOf(-111111111, "abc@example.com", JSONObject().put("id1", "value1")),
arrayOf(0, "abc@example.com", JSONObject().put("id1", "value1")),
arrayOf("abc", "abc@example.com", JSONObject().put("id1", "value1")),
arrayOf(null, "11111", JSONObject().put("id1", "value1")),
arrayOf(null, true, JSONObject().put("id1", "value1")),
arrayOf(22222222, "abc@example.com", null)
)
}

Copy function to the Rescue

If we look closely in the above example , each or most of the test data have only one or few parameters changing while others are being kept same. This is exactly the use case of data class’ copy function. Let’s have a look at the same example with all the test data represented using copy function.

@RunWith(Parameterized::class)
class IdTest(private val id: Any?, private val email: Any?, private val otherIds:Any?) {
data class Ids(
val id: Any?,
val email: Any?,
val otherIds: Any?
)

companion object {

val validId = Ids(1111111111, "abc@example.com", JSONObject().put("id1", "value1"))

@JvmStatic
@Parameterized.Parameters(name = "{index}: {0},{1},{2}")
fun createTestData() = arrayOf(
validId,
validId.copy(-111111111),
validId.copy(0),
validId.copy("abc"),
validId.copy(email = "abc"),
validId.copy(email = 11111),
validId.copy(email = true),
validId.copy(22222222, otherIds = null)
)
}
}

There you go, We have found one practical usage of data class’ copy function. In addition, the code looks cleaner and easier to generate more combination of test data with less code.

--

--