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 表达式作为参数,我们想测试它的行为。比如,我们有一个 CalculatorService
的 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
}
该方法接收一个 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 获取。