Easier Testing with Kotlin Delegates
Tags: kotlin, testing, android
Published: 2021-12-10 17:55:32

### TL;DR
You can use constructor injection of interfaces along with Kotlin delegates to make your code more modular without changing your class hierarchy. This has the benefit of not only making testing easier, but also code reuse, modularity, and easier dependency injection.

### The Problem
Recently I had the opportunity to simplify a Kotlin class hierarchy for a client. These aren't the actual classes, but the structure was something like this:

```kotlin
interface Task {
    val scope: CoroutineScope
        get() = GlobalScope

    suspend fun doSomething(taskName: String): Unit {
        scope.launch {
            delay(1000L)
            print("I am $taskName")
        }
    }
}

interface Coffee: Task {
    fun makeCoffee() {
        print("Drip Drip")
        scope.launch {
            doSomething("making coffee")
        }
    }

    fun drinkCoffee() {
        print("Mmmmm...")
        scope.launch {
            doSomething("drinking coffee")
        }
    }
}

interface Breakfast: Task {
    fun eatBreakfast() {
        print("Om nom nom")
        scope.launch {
            doSomething("eating breakfast")
        }
    }
}

interface Morning:
    Task,
    Coffee,
    Breakfast

class DefaultMorning: Morning {
    fun morningRoutine() {
        makeCoffee()
        drinkCoffee()
        eatBreakfast()
    }

    override suspend fun doSomething(taskName: String) {
        scope.launch {
            delay(15000L)    
            println("I'm tired and I'm $taskName")
        }
    }
}
```

We have a couple layers of inheritance, using an interface of interfaces. If we want to test it without the delay, we'll need to override the `doSomething()` method. If we do that, we're not actually testing the `DefaultMorning`. Rather, we're testing something similar to `DefaultMorning`:


```kotlin
class TestMorning: Morning {
    fun morningRoutine() {
        makeCofee()
        drinkCoffee()
        eatBreakfast()
    }

    override suspend fun doSomething(taskName: String) {
        println("I'm tired and I'm $taskName")
    }
}
```

We'll have to do the same thing for testing other classes as well:

```kotlin
class TestCoffee: Coffee {
    override suspend fun doSomething(taskName: String) {
        print("I am $taskName")
    }
}
```

This begs the question, "Is there a better way?" 

### Enter Kotlin Delegates
Merriam-Webster defines [delegate](https://www.merriam-webster.com/dictionary/delegate) as a verb and a noun, and I think the former is more relevant:
> 1: to entrust to another
  // "delegate authority"
  // "delegated the task to her assistant"

That's what we want to do. We don't want `DefaultRoutine` to implement any interfaces itself. Instead, we want to delegate those implementations to someone else. We'll pass them as parameters that we can substitute during testing, without creating another class. Let's see what that would look like, starting at the bottom of our class hierarchy.

```kotlin
// Removing default interface impl for clarity
interface Task {
    val scope: CoroutineScope
    suspend fun doSomething(taskName: String): Unit
}

interface Coffee: Task {
    fun makeCoffee()
    fun drinkCoffee()
}

class DefaultCoffee(private val task: Task): 
    Task by task,
    Coffee {
    fun makeCoffee() {
        print("Drip Drip")
        scope.launch {
            doSomething("making coffee")
        }
    }

    fun drinkCoffee() {
        print("Mmmmm...")
        scope.launch {
            doSomething("drinking coffee")
        }
    }
}
```

### The Upside

What advantage does this have? Now we can more easily control `DefaultCoffee`'s implementation, which comes in handy for testing. In production we'll use `DefaultTask`:

```kotlin
class DefaultTask: Task {
    override val scope: CoroutineScope
        get() = GlobalScope

    override suspend fun doSomething(taskName: String): Unit {
        scope.launch {
            // Some long network request
            delay(2000L)
            print("I am $taskName")
        }
    }
}
```
And we might have a `Task` implementation for testing:

```kotlin
class TestTask: Task {
    override val scope: CoroutineScope
        get() = TestScope()

    override suspend fun doSomething(taskName: String): Unit {
        scope.launch {
            print("I am $taskName")
        }
    }
}
```
We want to delegate the `Task` interface to some implementation, so `DefaultCoffee` doesn't need to implement it, or rely on default implementations. We'll do that using a Kotlin delegate. The delegate syntax uses the `by` keyword, to say, "I implement this `Task` interface by delegating it to some implementation." 

To get there, we'll change `Coffee` a bit:
```kotlin
interface Coffee: Task {
    fun makeCoffee()
    fun drinkCoffee()
}

class DefaultCoffee(private val task: Task): 
    Task by task,
    Coffee {
    override fun makeCoffee() {
        print("Drip Drip")
        scope.launch {
            doSomething("making coffee")
        }
    }

    override fun drinkCoffee() {
        print("Mmmmm...")
        scope.launch {
            doSomething("drinking coffee")
        }
    }
}
``` 
Now this lets us test the `DefaultCoffee` class directly, by swapping out `Task` implementations:
```kotlin
val defaultTask = DefaultTask()
val testTask = TestTask()

val defaultCoffee = DefaultCoffee(defaultTask)
val testCoffee = DefaultCoffee(testTask)
```
No mocks or extending the actual class under test required!

### Putting it all together

We can do the same thing with `Breakfast`:
```kotlin
interface Breakfast: Task {
    fun eatBreakfast()
}

class DefaultBreakfast(private val task: Task): Breakfast, Task by task {
    override fun eatBreakfast() {
        print("Om nom nom")
        scope.launch {
            doSomething("eating breakfast")
        }
    }
}

val defaultBreakfast = DefaultBreakfast(defaultTask)
val testBreakfast = DefaultBreakfast(testTask)
```

Now we can do the same thing with `Morning`:
```kotlin
interface Morning :
    Task,
    Coffee,
    Breakfast

class DefaultMorning(
    private val task: Task,
    private val coffee: Coffee,
    private val breakfast: Breakfast
) : Morning,
    Task by task,
    Coffee by coffee,
    Breakfast by breakfast {
    override val scope: CoroutineScope
        get() = task.scope

    override suspend fun doSomething(taskName: String) =
        task.doSomething(taskName)
}
```
In this way, we are *composing* `DefaultMorning` from several implementations of interfaces via delegates.

This is in contrast to:
1. *Inheriting* implementations via an abstract class
2. Relying on default implementations
3. Making `DefaultMorning` implement everything itself

Unlike 1, we don't need a deep class hierarchy. Unlike 2, we can test our class directly, and don't need to make the default implementation `open`. Unlike 3, `DefaultMorning` is terse.

Through this composition, we can still have an "is a" relationship that might be required by whatever API we're working with, but they can be implemented in a "has a" sort of way.

### :question: Isn't this redundant?

You may reasonably ask, "Why do we need to override `scope` and `doSomething()` if they're already implemented by our delegate(s)?

If you omit them, IntelliJ will tell you something like:

> ```Class 'DefaultMorning' must override public open suspend fun doSomething(taskName: String): Unit defined in com.mynamespace.DefaultMorning because it inherits many implementations of it``` 

This is because the `Task`, `Coffee`, and `Breakfast` delegates all implement the `Task` interface, and the compiler doesn't know which one to use. (Even though we told it specifically which implementation we want (`by defaultTask`) to use). So we have to delegate those calls (more) explicitly.

### :warning: Don't hardcode delegates

If you don't pass delegates into the constructor, like this:
```kotlin
class DefaultCoffee(private val task: Task) :
    Task by task, Coffee { /*...*/ }
```
but hardcode them like this:
```kotlin
class DefaultCoffee() : 
    Task by DefaultTask(), Coffee { /*...*/ }
```
then you are tightly coupling that `Task` implementation to `DefaultCoffee`. Swapping out implementations will be difficult, defeating our major purpose of using delegates. You might need to do some mock gymnastics and/or override methods of the class under test with an `object` declaration  :tired_face:. Instead, treat yourself to a pleasurable testing experience, and practice constructor injection of your delegates :smile:. 

### Conclusion
That's all I got for today! Delegates seemed strange when I first learned about them, but after struggling with testing things that heavily relied on inheritance and default interface implementations, I found delegates helped me test with ease. I hope it helps you as well. 

If you have questions or comments, let me know in the comments below. You can also reach me on Twitter at [@AielloBrady](https://twitter.com/AielloBrady), or on the Kotlin Slack. And if you find this interesting, and might like to [work with](https://touchlab.co/contact-us/) or [work at](https://touchlab.co/careers-3/) Touchlab, we'd love to hear from you!

```kotlin
class Brady(val fingers: Typing): Typing by fingers
```