无需Mockito/MockK等框架,如何验证单元测试中方法的调用?
脱离Mock框架验证无返回值函数调用的手动实现方案
核心思路是自定义测试用的Toaster实现类,在类内部维护每个函数的调用记录(调用次数、传入参数等),通过自带的断言逻辑完成验证,完全不需要依赖MockK、Mockito这类框架。
方式一:为每个方法单独维护调用记录(直观易读)
这种方案针对每个接口方法单独记录调用信息,验证逻辑更直观:
class TestToaster: Toaster { // 分别记录不同重载方法的调用参数 private val successStringCalls = mutableListOf<String>() private val successIntCalls = mutableListOf<Int>() private val errorStringCalls = mutableListOf<String>() private val errorIntCalls = mutableListOf<Int>() override fun showSuccessMessage(message: String) { successStringCalls.add(message) } override fun showSuccessMessage(message: Int) { successIntCalls.add(message) } override fun showErrorMessage(message: String) { errorStringCalls.add(message) } override fun showErrorMessage(message: Int) { errorIntCalls.add(message) } // 验证带String参数的showSuccessMessage调用情况 fun verifyShowSuccessMessageCalled(times: Int = 1, message: String? = null) { val actualCount = message?.let { msg -> successStringCalls.count { it == msg } } ?: successStringCalls.size check(actualCount == times) { "Expected showSuccessMessage(String) to be called $times times" + (message?.let { " with message '$it'" } ?: "") + ", but was called $actualCount times" } } // 验证带Int参数的showSuccessMessage调用情况 fun verifyShowSuccessMessageCalled(times: Int = 1, message: Int? = null) { val actualCount = message?.let { msg -> successIntCalls.count { it == msg } } ?: successIntCalls.size check(actualCount == times) { "Expected showSuccessMessage(Int) to be called $times times" + (message?.let { " with message '$it'" } ?: "") + ", but was called $actualCount times" } } // 同理实现错误提示方法的验证 fun verifyShowErrorMessageCalled(times: Int = 1, message: String? = null) { val actualCount = message?.let { msg -> errorStringCalls.count { it == msg } } ?: errorStringCalls.size check(actualCount == times) { "Expected showErrorMessage(String) to be called $times times" + (message?.let { " with message '$it'" } ?: "") + ", but was called $actualCount times" } } fun verifyShowErrorMessageCalled(times: Int = 1, message: Int? = null) { val actualCount = message?.let { msg -> errorIntCalls.count { it == msg } } ?: errorIntCalls.size check(actualCount == times) { "Expected showErrorMessage(Int) to be called $times times" + (message?.let { " with message '$it'" } ?: "") + ", but was called $actualCount times" } } // 重置调用记录,方便复用测试实例 fun reset() { successStringCalls.clear() successIntCalls.clear() errorStringCalls.clear() errorIntCalls.clear() } }
使用示例
在测试代码中初始化实例、执行业务逻辑后,直接调用验证方法:
// 初始化测试用Toaster val toaster = TestToaster() // 执行被测试的业务逻辑(比如触发showSuccessMessage的操作) userService.handleLoginSuccess(toaster) // 验证方法被调用,且参数匹配 toaster.verifyShowSuccessMessageCalled(message = "登录成功") // 验证方法被调用2次 toaster.verifyShowSuccessMessageCalled(times = 2)
方式二:通用调用记录方案(适合多方法场景)
如果接口方法较多,可以用统一的结构记录所有调用,减少重复代码:
// 定义数据类存储调用信息 data class MethodCall( val methodName: String, val arguments: List<Any?> ) class TestToaster: Toaster { private val allCalls = mutableListOf<MethodCall>() override fun showSuccessMessage(message: String) { allCalls.add(MethodCall("showSuccessMessage", listOf(message))) } override fun showSuccessMessage(message: Int) { allCalls.add(MethodCall("showSuccessMessage", listOf(message))) } override fun showErrorMessage(message: String) { allCalls.add(MethodCall("showErrorMessage", listOf(message))) } override fun showErrorMessage(message: Int) { allCalls.add(MethodCall("showErrorMessage", listOf(message))) } // 通用验证方法,支持指定方法名、调用次数和参数 fun verifyMethodCalled(methodName: String, times: Int = 1, arguments: List<Any?>? = null) { val matchingCalls = if (arguments != null) { allCalls.filter { it.methodName == methodName && it.arguments == arguments } } else { allCalls.filter { it.methodName == methodName } } check(matchingCalls.size == times) { "Expected method '$methodName' to be called $times times" + (arguments?.let { " with arguments $it" } ?: "") + ", but was called ${matchingCalls.size} times" } } fun reset() = allCalls.clear() }
使用示例
val toaster = TestToaster() // 执行业务逻辑 orderService.handleOrderFailure(toaster) // 验证带Int参数的showErrorMessage被调用1次 toaster.verifyMethodCalled("showErrorMessage", arguments = listOf(500)) // 验证showSuccessMessage从未被调用 toaster.verifyMethodCalled("showSuccessMessage", times = 0)
核心原理
所有验证逻辑依赖Kotlin自带的check函数:当实际调用情况不符合预期时,check会抛出IllegalStateException,测试框架(如JUnit、Spek)会自动捕获该异常并标记测试失败。这种方式完全不需要引入额外依赖,实现轻量且可控的测试验证。
内容的提问来源于stack exchange,提问作者Sermilion
相关产品推荐
相关产品推荐

