Better Kotlin unit testing with mock helper functions

Pravin Sonawane
Mar 6 · 4 min read

Kotlin with its default and named arguments makes the call site code of a function elegant and readable.

In this article, I’ll be sharing my technique of using these language constructs to create helper functions for better mocking.

Scenario

Imagine a complex type like Company as shown below. Company composes many nested types like Employee, CompanyEvent, Product which themselves are complex types. (You don’t have to fully understand these types but just that such composition can get huge even for a moderate sized application).

package example

data class Company(
    val id: String,
    val name: String,
    val type: CompanyType,
    val employees: List<Employee> = emptyList(),
    val events: List<CompanyEvent> = emptyList(),
    val products: List<Product> = emptyList(),
    val address: Address,
    val entityState: State = State.ACTIVE
)

data class Employee(
    val id: String,
    val name: String,
    val address: Address,
    val salary: Amount,
    val entityState: State = State.ACTIVE
)

data class Address(
    val id: String,
    val line1: String,
    val line2: String,
    val city: String,
    val state: String,
    val zip: String,
    val entityState: State = State.ACTIVE
)

enum class State {
    ACTIVE,
    INACTIVE
}

Why are my unit tests ugly?!

Writing unit tests for functions that return a Company would be too laborious and unreadable. Here’s an example:

// Class under test
class GetCompaniesUseCase(val companyService: CompanyService) {
    fun execute(companyIds: List<String>): List<Company> {..}
}

Here’s a what a unit test would look like

class CreateCompanyUseCaseTests {

    @Test
    fun `GIVEN companies already created WHEN queried by their ID THEN a list of such companies should be returned`() {

Mock helpers to the rescue!

What we really want is a consistent strategy to write easy to read unit tests. Also our tests fixtures should be configurable as required. What if we could simply say something like

val company = mockCompany() // creates the entire Company graph!

Well, mock helper functions are exactly what we need. Lets look at how we can write a mock helper function for Address

fun mockAddress(
    mockId: Int,
    id: String = "id$mockId",
    line1: String = "first line$mockId",
    line2: String = "second line$mockId",
    city: String = "city$mockId",
    state: String = "state$mockId",
    zip: String = "zip$mockId",
    entityState: State = State.ACTIVE
): Address {
    return Address(
        id = id,
        line1 = line1,
        line2 = line2,
        city = city,
        state = state,
        zip = zip,
        entityState = entityState
    )
}

There’s a lot going on here. I’ll summarize the key takeaways below:

  1. By design, no property of Address is nullable or has default values. mockAddress allows us to provide default values for all properties so we don’t have to specify property values unimportant to the current test.
  2. mockAddress provides an additional mockId parameter. This parameter is used to create unique instances of Address.
  3. mockXxx functions for classes that do not explicitly have an ID (example: Amount) can benefit from mockId to create unique instances of these classes.
  4. If a unit test requires a different value other than the default value provided by mockAddress, it can do so as follows
val batmanAddress = mockAddress(mockId = 1, city = "Gotham City")

A mock helper function for the Company type

Similar to mockAddress, we can create a mockCompany function as follows

fun mockCompany(
    mockId: Int,
    id: String = "id$mockId",
    name: String = "name$mockId",
    type: CompanyType = CompanyType.PUBLIC,
    employees: List<Employee> = listOf(mockEmployee(mockId)),
    events: List<CompanyEvent> = listOf(mockCompanyEvent(mockId)),
    products: List<Product> = listOf(mockProduct(mockId)),
    address: Address = mockAddress(mockId),
    entityState: State = State.ACTIVE
): Company {
    return Company(
        id = id,
        name = name,
        type = type,
        employees = employees,
        events = events,
        products = products,
        address = address
    )
}

Few takeaways from the code above are as follow:

  1. An important thing to note here is how mockId is forwarded to other mockXxx functions by mockCompany.
  2. If mockCompany should provide a list of two mock products, the above function can be modified as follows:
fun mockCompany(
    mockId: Int,
...
   products: List<Product> = listOf(mockProduct(1), mockProduct(2)),
...
): Company {
    return Company(...)
}

Finally, a better unit test!

Armed with mock helpers, we are finally able to write a clean unit test!

class GetCompaniesUseCaseTests {

Thanks for reading and hope you found it useful. Let me know your thoughts on it. :)

Fin.

Pravin Sonawane

Written by

Sr Software Engineer @ Uber