Kotlin - Where Code Reads Like Prose

Captivating Kotlin features that will win you over!
Ishan Kute_avatar
Ishan Kute
Jul 25, 2022 | 6 min read

At Medly, we constantly experiment with the latest technologies so that we can build applications in an efficient and scalable way. Using different tools & languages allows us to modernize our codebase to a great extent! With that being said, Kotlin tops the list of our current favorite latest technologies! Kotlin's official website introduces Kotlin as "a modern programming language that makes developers happier," and it certainly does!

Kotlin’s Code Readability

At Medly, we use Kotlin for server-side backend programming and follow the TDD approach. The most impressive thing about Kotlin is its code readability. Kotlin's highly elegant testing framework, Kotest, and mocking framework, Mockk, demonstrate how Kotlin can be used to write a code that reads like prose.

For instance, check this unit test

"should create a new user" {  
    every { userRepository.createUser(user) } returns id  
	val result = userService.createUser(user)  
	verify(exactly = 1) { userRepository.createUser(user) }  
	result shouldBe id
}

The first thing that catches our eye is the test name written in a plain string without any parenthesis or the nominal it function call that we see in JavaScript-based testing frameworks. The next would be the stub for the createUser method, which reads like an English sentence with the use of words like every and returns. The same is the case with a verify statement. And lastly, the assertion statement also looks like an English statement with the use of shouldBe. This is so elegant and readable. Let's see how Kotest and Mockk used Kotlin features to make all this possible.

‘Verify’ Statement

Let's start with understanding the simple verify statement. In Kotlin, functions are first-class, which means they can be stored in variables and can be passed as arguments to functions. Consider the following function, which adds two integers.

fun add(a: Int, b: Int): Int {  
    return a + b  
}

While storing this function in a variable we should avoid writing the function name and instead use the variable name as the function name as follows:

val add: (Int, Int) -> Int = fun(a: Int, b: Int): Int {  
    return a + b  
}

(Int, Int) -> Int is known as a function type.
With type inference and function expression, this can be much more simplified as follows:

val add = fun(a: Int, b: Int) = a + b

We can simplify this further by using the lambda expression for a function.

val add = {a: Int, b: Int -> a + b}

In case the function has no input arguments, we can even omit writing the arrow ->

// Both of these are the representations of the same function
val printHello = fun(){ return println("Hello") }  
val printHelloLambda = { println("Hello") }

The verify statement that we saw in the test case above is actually a function call with the second parameter of a function type () -> Unit

verify(  
    exactly = 1,   
	verifyBlock = { userRepository.createUser(user) }  
)

According to the Kotlin convention, if the last parameter of a function is a function, then a lambda expression passed as the corresponding argument can be placed outside the parentheses. This transforms the above function call into what we saw in the test case:

verify(exactly = 1) { userRepository.createUser(user) }

Assertion Statement

Now, let's check the magic going behind the last assertion statement:

result shouldBe id

To understand this, we need to first understand the extension functions and infix functions.

Extension functions allow you to extend a class without inheriting it.

class Example {  
    fun somePredefinedFunctionality() = 
	    println("Predefined functionality")  
}  

// Extending Example class with an additional functionality
fun Example.extendedFunctionality() = 
	println("Extended functionality")  
  
val example = Example()  
example.extendedFunctionality() //prints "Extended functionality"

If the function has just a single parameter with no default value, then we can make that function an infix function by using the infix keyword. The beauty of the infix function is that it can be called without using a dot and parentheses. Check the following example:

infix fun String.hasLength(length: Int) = this.length == length  
  
"Hello" hasLength 5 // evaluates to true

With the knowledge of extension and infix functions, we can easily understand that the assertion statement in the test case is actually a function call on the result with id as the only input parameter.

result.shouldBe(id)

// is similar to

result shouldBe id

This also explains the stub statement.

every({ userRepository.createUser(user) }).returns(id)

//is similar to

every { userRepository.createUser(user) } returns id

String Test Name

Finally, let's check the magic behind the test name with simple strings.
To understand this we need to first understand the Kotlin feature of Operator overloading.

Kotlin allows you to provide a custom implementation for the predefined set of operators on types. To override an operator we need to write a special kind of operator function with a specific name for the corresponding type. Kotlin documentation has this perfect example of operator overloading:

data class Point(val x: Int, val y: Int)

operator fun Point.unaryMinus() = Point(-x, -y)

val point = Point(10, 20)

fun main() {
   println(-point)  // prints "Point(x=-10, y=-20)"
}

In this example, - operator is being overridden for which unaryMinus is the specific name. You can find the list of such operators and their specific names in the Kotlin documentation.

In Kotlin, even the parentheses () is an operator. It is an operator for invoking the function call. Therefore, you can overload this functionality with the specific invoke function.

To understand how Kotest made writing that test case name in plain string possible, consider, the example below. Here, we are overloading the invoke operator (). We are creating this operator function on the String type as an extension.

operator fun String.invoke(test: () -> Unit) = test()

"should print something"({ println("something") })//prints "something"

// this can also be written as

"should print something" {  
    println("something")  
}

Nice! This explains all the magic now.

Take a look at the same test case again, and you'll understand the magic behind this elegant-looking piece of code.

"should create a new user" {  
    every { userRepository.createUser(user) } returns id  
	val result = userService.createUser(user)  
	verify(exactly = 1) { userRepository.createUser(user) }  
	result shouldBe id
}

Many other features help writing Kotlin code in a readable way, but let's explore them in one of the future blogs.

Concluding Thoughts

That’s all from this blog. We hope you find this blog helpful and inspire you to explore and try Kotlin. Alternatively, if you want to learn more about Medly, please visit our website at Medly.Tech.