1. Introduction

One of the many powerful features that Kotlin employs is lambda expressions. Such expressions are called function literals. They aren’t declared but passed as an expression to the receiving method at the call site.

In this short tutorial, we’ll have a quick look at how we can unit test such expressions using the mockito-kotlin testing library. It implements some helpful extensions to the commonly used Mockito library.

2. Setup Mockito Kotlin

We can start using mockito-kotlin by adding its dependency in the pom.xml of our Maven project:

<dependency>
    <groupId>org.mockito.kotlin</groupId>
    <artifactId>mockito-kotlin</artifactId>
    <version>5.1.0</version>
    <scope>test</scope>
</dependency>

3. Capturing a Lambda Function

To test our lambda expression, we’ll use Mockito’s ArgumentCaptor. It allows us to capture an argument passed to a method to inspect it. This is especially useful when we can’t access the argument outside the method we’d like to test.

3.1. Problem at Hand

Let’s consider a class method that accepts a lambda expression we require to test as an argument. To demonstrate our problem, we’re testing a CalculatorService‘s method calculate():

class CalculatorService(private val mathOperations: MathOperations) {
    fun calculate(operation: (a: Int, b: Int) -> Int): Int {
        return mathOperations.performOperation(operation)
    }
}
interface MathOperations {
    fun performOperation(operation: (a: Int, b: Int) -> Int): Int
}

The method takes a lambda expression and executes it using the MathOperations interface.

To guarantee the execution of the provided lambda and return its result, we can capture the argument passed to the MathOperations interface using Mockito’s ArgumentCaptor. If we’ve used it before, we may know that it’s instantiated by the definition of ArgumentCaptor.forClass(Class clazz).

However, if we try to use it with a lambda expression, we’ll encounter a NullPointerException when capturing the execution:

val operation: (Int, Int) -> Int = { a, b -> a + b }
val lambdaCaptor = ArgumentCaptor.forClass(operation::class.java)
 
calculatorService.calculate(operation)
 
verify(mathOperations).performOperation(lambdaCaptor.capture())
java.lang.NullPointerException: lambdaCaptor.capture() must not be null

3.2. Solution

mockito-kotlin comes to our rescue by defining an inline function with a reified type parameter:

inline fun <reified T : Any> argumentCaptor() = ArgumentCaptor.forClass(T::class.java)

By using such a construct, we can easily instantiate an ArgumentCaptor for our lambda expression:

val lambdaCaptor = argumentCaptor<(Int, Int) -> Int>()

This allows us to invoke the lambda and verify its result:

@Test
fun `performs addition operation`() {
    // given
    val mathOperations = mock<MathOperations>()
    val calculatorService = CalculatorService(mathOperations)

    // when
    calculatorService.calculate { a, b -> a + b }

    // then
    val lambdaCaptor = argumentCaptor<(Int, Int) -> Int>()

    verify(mathOperations).performOperation(lambdaCaptor.capture())

    val capturedOperation = lambdaCaptor.firstValue
    val operationResult = capturedOperation(11, 3)

    assertEquals(14, operationResult)
}

4. Conclusion

In this quick article, we’ve learned how to test a lambda expression by capturing it using the mockito-kotlin library. The library allows us to invoke the captured lambda and test its execution result.

As usual, we can find the source code for the above examples on GitHub.