1. 引言

Kotlin 的一大亮点就是对 lambda 表达式 的支持。这类表达式被称为函数字面量(function literals),它们不是通过声明定义的,而是在调用处以表达式的形式传入目标方法。

在本篇简短教程中,我们重点来看如何使用 mockito-kotlin 测试库来对这类 lambda 表达式进行单元测试。该库为广泛使用的 Mockito 提供了一些实用的 Kotlin 扩展。

2. 配置 Mockito Kotlin

要在 Maven 项目中使用 mockito-kotlin,只需在 pom.xml 中添加如下依赖:

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

Gradle 用户可以使用:

testImplementation "org.mockito.kotlin:mockito-kotlin:5.1.0"

3. 捕获 Lambda 函数

为了测试 lambda 表达式,我们会使用 Mockito 提供的 ArgumentCaptor。它允许我们捕获传入方法的参数,并对其进行检查。**当该参数仅在测试方法内部可见时,这一功能尤为有用。

3.1. 遇到的问题

我们来看一个例子,假设有一个类方法接收一个 lambda 表达式作为参数,我们想测试它的行为。比如,我们有一个 CalculatorServicecalculate() 方法:

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
}

该方法接收一个 lambda 表达式,并通过 MathOperations 接口执行它。

如果我们尝试使用标准的 ArgumentCaptor.forClass() 来捕获这个 lambda:

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

这是因为 Kotlin 的 lambda 表达式在 JVM 上的实现方式和 Java 不同,直接使用 forClass() 无法正确捕获其类型。

3.2. 解决方案

mockito-kotlin 提供了一个非常简洁的扩展函数:

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

通过这个带有 reified 类型参数 的函数,我们可以这样创建一个 ArgumentCaptor

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

然后我们就可以捕获 lambda 并验证其执行结果:

@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)
}

✅ 这样就成功地捕获了 lambda,并验证了其逻辑是否正确。

⚠️ 如果你直接使用 ArgumentCaptor.forClass(),可能会遇到类型擦除或空指针问题,记得优先使用 mockito-kotlin 提供的 argumentCaptor() 扩展函数。

4. 小结

通过 mockito-kotlin 提供的扩展函数,我们可以轻松地捕获并测试 lambda 表达式的执行逻辑。这在验证回调、事件处理等场景中非常实用。

完整代码示例可在 GitHub 获取。


原始标题:Testing a Lambda Function With Mockito Kotlin