欢迎访问我的主页: https://heeheeaii.github.io/
主要接口和函数
核心创建函数
mockk<T>()- 创建 mock 对象spyk<T>()- 创建 spy 对象(部分模拟)relaxedMockk<T>()- 创建宽松的 mock 对象mockkStatic()- 模拟静态函数mockkObject()- 模拟 object 单例mockkConstructor()- 模拟构造函数
1. mockk<T>() - 创建标准 mock 对象
作用:创建一个完全假的对象,所有方法都需要手动设置行为。
interface UserService {
fun getUser(id: String): User
fun deleteUser(id: String)
}
@Test
fun `mockk 标准用法`() {
// 创建完全假的对象
val mockService = mockk<UserService>()
// 必须手动设置每个方法的行为,否则调用会报错
every { mockService.getUser(any()) } returns User("123", "张三")
justRun { mockService.deleteUser(any()) }
// 现在可以正常使用
val user = mockService.getUser("123") // 返回我们设置的假数据
mockService.deleteUser("123") // 不做任何事情
verify { mockService.getUser("123") }
verify { mockService.deleteUser("123") }
}
2. spyk<T>() - 创建 spy 对象(部分模拟)
作用:基于真实对象创建,只模拟你指定的方法,其他方法使用真实实现。
// 真实的类
open class Calculator {
open fun add(a: Int, b: Int): Int = a + b
open fun multiply(a: Int, b: Int): Int = a * b
open fun complexCalculation(a: Int, b: Int): Int {
return add(a, b) * multiply(a, b) // 调用自己的其他方法
}
}
@Test
fun `spyk 部分模拟`() {
// 基于真实对象创建 spy
val spyCalculator = spyk<Calculator>()
// 只模拟 add 方法,其他方法用真实实现
every { spyCalculator.add(any(), any()) } returns 100
val result = spyCalculator.complexCalculation(2, 3)
// 结果 = 100(假的add) * 6(真的multiply) = 600
println("结果: $result") // 600
// 验证真实方法和模拟方法都被调用了
verify { spyCalculator.add(2, 3) } // 模拟的方法
verify { spyCalculator.multiply(2, 3) } // 真实的方法
}
// 也可以基于现有对象创建 spy
@Test
fun `基于现有对象创建 spy`() {
val realCalculator = Calculator()
val spy = spyk(realCalculator) // 基于现有对象
// 只模拟特定方法
every { spy.add(1, 1) } returns 999
println(spy.add(1, 1)) // 999 (模拟的)
println(spy.add(2, 3)) // 5 (真实的)
println(spy.multiply(4, 5)) // 20 (真实的)
}
3. relaxedMockk<T>() - 创建宽松的 mock 对象
作用:创建"懒人版"mock,不需要设置 every returns,会自动返回默认值。
interface ApiService {
fun getString(): String
fun getInt(): Int
fun getBoolean(): Boolean
fun getUser(): User?
fun saveData(data: String)
}
@Test
fun `relaxedMockk 宽松模式`() {
// 宽松 mock,不需要设置 every returns
val relaxedMock = relaxedMockk<ApiService>()
// 直接调用,会返回默认值
println(relaxedMock.getString()) // "" (空字符串)
println(relaxedMock.getInt()) // 0
println(relaxedMock.getBoolean()) // false
println(relaxedMock.getUser()) // null
relaxedMock.saveData("test") // 不报错,什么都不做
// 仍然可以验证调用
verify { relaxedMock.getString() }
verify { relaxedMock.saveData("test") }
}
@Test
fun `对比标准 mock`() {
val standardMock = mockk<ApiService>()
try {
val result = standardMock.getString() // ❌ 报错!没有设置行为
} catch (e: Exception) {
println("标准 mock 报错: ${e.message}")
}
val relaxedMock = relaxedMockk<ApiService>()
val result = relaxedMock.getString() // ✅ 正常,返回 ""
println("宽松 mock 结果: '$result'")
}
4. mockkStatic() - 模拟静态函数
作用:模拟静态方法或顶级函数。
// 假设有静态工具类
object TimeUtils {
fun getCurrentTimestamp(): Long = System.currentTimeMillis()
fun formatTime(timestamp: Long): String = "formatted_time"
}
// 或者顶级函数
fun getCurrentUserId(): String = "real_user_id"
@Test
fun `mockkStatic 模拟静态方法`() {
// 模拟 object 的静态方法
mockkStatic(TimeUtils::class)
every { TimeUtils.getCurrentTimestamp() } returns 1234567890L
every { TimeUtils.formatTime(any()) } returns "fake_time"
val timestamp = TimeUtils.getCurrentTimestamp()
val formatted = TimeUtils.formatTime(timestamp)
println("时间戳: $timestamp") // 1234567890
println("格式化时间: $formatted") // fake_time
verify { TimeUtils.getCurrentTimestamp() }
verify { TimeUtils.formatTime(1234567890L) }
// 清理(重要!)
unmockkStatic(TimeUtils::class)
}
@Test
fun `模拟顶级函数`() {
// 模拟顶级函数(需要指定包名)
mockkStatic("com.example.UtilsKt") // 假设函数在这个包里
every { getCurrentUserId() } returns "fake_user_123"
val userId = getCurrentUserId()
println("用户ID: $userId") // fake_user_123
verify { getCurrentUserId() }
unmockkStatic("com.example.UtilsKt")
}
5. mockkObject() - 模拟 object 单例
作用:模拟 Kotlin 的 object 单例对象。
// Kotlin object 单例
object DatabaseConfig {
fun getConnectionUrl(): String = "jdbc:postgresql://localhost:5432/prod"
fun getMaxConnections(): Int = 100
val driverName: String = "PostgreSQL Driver"
}
@Test
fun `mockkObject 模拟单例对象`() {
// 模拟整个 object
mockkObject(DatabaseConfig)
every { DatabaseConfig.getConnectionUrl() } returns "jdbc:h2:mem:testdb"
every { DatabaseConfig.getMaxConnections() } returns 5
val url = DatabaseConfig.getConnectionUrl()
val maxConn = DatabaseConfig.getMaxConnections()
println("数据库URL: $url") // jdbc:h2:mem:testdb
println("最大连接数: $maxConn") // 5
verify { DatabaseConfig.getConnectionUrl() }
verify { DatabaseConfig.getMaxConnections() }
// 清理(重要!)
unmockkObject(DatabaseConfig)
}
@Test
fun `object 属性模拟`() {
mockkObject(DatabaseConfig)
// 可以模拟属性
every { DatabaseConfig.driverName } returns "H2 Test Driver"
println("驱动名称: ${DatabaseConfig.driverName}") // H2 Test Driver
unmockkObject(DatabaseConfig)
}
6. mockkConstructor() - 模拟构造函数
作用:拦截类的构造函数,让 new 操作返回 mock 对象。
// 要模拟的类
class DatabaseConnection(private val url: String) {
fun connect(): Boolean = true
fun query(sql: String): List<String> = listOf("real_data")
fun close() {}
}
// 使用这个类的业务代码
class UserRepository {
fun getAllUsers(): List<String> {
val conn = DatabaseConnection("jdbc://prod") // 创建真实连接
conn.connect()
val result = conn.query("SELECT * FROM users")
conn.close()
return result
}
}
@Test
fun `mockkConstructor 模拟构造函数`() {
// 模拟 DatabaseConnection 的构造函数
mockkConstructor(DatabaseConnection::class)
// 设置所有通过构造函数创建的对象的行为
every { anyConstructed<DatabaseConnection>().connect() } returns true
every { anyConstructed<DatabaseConnection>().query(any()) } returns listOf("fake_user1", "fake_user2")
justRun { anyConstructed<DatabaseConnection>().close() }
val repository = UserRepository()
val users = repository.getAllUsers() // 内部会 new DatabaseConnection()
println("用户列表: $users") // [fake_user1, fake_user2]
// 验证构造出来的对象被正确调用了
verify { anyConstructed<DatabaseConnection>().connect() }
verify { anyConstructed<DatabaseConnection>().query("SELECT * FROM users") }
verify { anyConstructed<DatabaseConnection>().close() }
// 清理(重要!)
unmockkConstructor(DatabaseConnection::class)
}
记住:静态、对象、构造函数的 mock 用完要清理!
验证函数
verify()- 验证方法调用verifyAll()- 验证所有指定的调用verifyOrder()- 验证调用顺序verifySequence()- 验证严格的调用序列confirmVerified()- 确认所有期望都已验证
行为定义函数
every()- 定义方法的返回行为coEvery()- 定义协程函数的返回行为justRun()- 定义 Unit 返回类型的方法行为coJustRun()- 定义协程 Unit 返回类型的方法行为
参数匹配器
any()- 匹配任何参数eq()- 匹配相等的参数more()- 匹配大于指定值的参数less()- 匹配小于指定值的参数match()- 自定义匹配器slot()- 捕获参数值capture()- 捕获多个参数值
主要注解
@MockK
用于自动创建 mock 对象
@MockK
lateinit var userService: UserService
@SpyK
用于自动创建 spy 对象
@SpyK
var userRepository = UserRepositoryImpl()
@RelaxedMockK
用于自动创建宽松的 mock 对象
@RelaxedMockK
lateinit var apiService: ApiService
@InjectMockKs
自动注入 mock 依赖到测试对象中
@InjectMockKs
lateinit var userController: UserController
详细使用演示
1. 基础 Mock 使用
import io.mockk.*
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.BeforeEach
class BasicMockExample {
interface UserService {
fun getUser(id: String): User
fun updateUser(user: User): Boolean
}
data class User(val id: String, val name: String)
@Test
fun `基础 mock 使用`() {
// 创建 mock 对象
val mockUserService = mockk<UserService>()
// 定义行为
every { mockUserService.getUser("123") } returns User("123", "张三")
every { mockUserService.updateUser(any()) } returns true
// 使用
val user = mockUserService.getUser("123")
val result = mockUserService.updateUser(user)
// 验证
verify { mockUserService.getUser("123") }
verify { mockUserService.updateUser(any()) }
assert(user.name == "张三")
assert(result == true)
}
}
2. 使用注解的测试类
import io.mockk.*
import io.mockk.junit5.MockKExtension
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(MockKExtension::class)
class AnnotationMockExample {
@MockK
lateinit var userRepository: UserRepository
@MockK
lateinit var emailService: EmailService
@InjectMockKs
lateinit var userService: UserService
interface UserRepository {
fun findById(id: String): User?
fun save(user: User): User
}
interface EmailService {
fun sendEmail(to: String, subject: String): Boolean
}
class UserService(
private val userRepository: UserRepository,
private val emailService: EmailService
) {
fun createUser(name: String, email: String): User {
val user = User(generateId(), name, email)
val savedUser = userRepository.save(user)
emailService.sendEmail(email, "欢迎注册")
return savedUser
}
private fun generateId() = "user_${System.currentTimeMillis()}"
}
data class User(val id: String, val name: String, val email: String)
@Test
fun `使用注解创建用户`() {
val user = User("123", "李四", "lisi@example.com")
// 定义 mock 行为
every { userRepository.save(any()) } returns user
every { emailService.sendEmail(any(), any()) } returns true
// 执行测试
val result = userService.createUser("李四", "lisi@example.com")
// 验证
verify { userRepository.save(any()) }
verify { emailService.sendEmail("lisi@example.com", "欢迎注册") }
assert(result.name == "李四")
}
}
3. Spy 对象使用
class SpyExample {
open class MathCalculator {
open fun add(a: Int, b: Int): Int = a + b
open fun multiply(a: Int, b: Int): Int = a * b
open fun complexCalculation(a: Int, b: Int): Int {
return add(a, b) * multiply(a, b)
}
}
@Test
fun `spy 对象使用`() {
val calculator = spyk<MathCalculator>()
// 只模拟特定方法,其他方法使用真实实现
every { calculator.add(any(), any()) } returns 100
val result = calculator.complexCalculation(2, 3)
// verify 真实方法和模拟方法都被调用
verify { calculator.add(2, 3) }
verify { calculator.multiply(2, 3) }
// 结果 = 100(模拟的add结果) * 6(真实的multiply结果) = 600
assert(result == 600)
}
}
4. 参数捕获和匹配
class ParameterCaptureExample {
interface Logger {
fun log(level: String, message: String, metadata: Map<String, Any>)
}
@Test
fun `参数捕获和匹配`() {
val mockLogger = mockk<Logger>()
val messageSlot = slot<String>()
val metadataSlot = slot<Map<String, Any>>()
justRun {
mockLogger.log(
eq("INFO"),
capture(messageSlot),
capture(metadataSlot)
)
}
// 使用 logger
mockLogger.log("INFO", "用户登录", mapOf("userId" to "123", "ip" to "192.168.1.1"))
// 验证捕获的参数
verify { mockLogger.log("INFO", any(), any()) }
assert(messageSlot.captured == "用户登录")
assert(metadataSlot.captured["userId"] == "123")
}
@Test
fun `自定义参数匹配器`() {
val mockLogger = mockk<Logger>()
justRun { mockLogger.log(any(), any(), any()) }
mockLogger.log("ERROR", "数据库连接失败", emptyMap())
mockLogger.log("INFO", "用户登录成功", emptyMap())
// 使用自定义匹配器验证错误日志
verify {
mockLogger.log(
match { it.startsWith("ERROR") },
match { it.contains("数据库") },
any()
)
}
}
}
5. 协程支持
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.delay
class CoroutineExample {
interface AsyncUserService {
suspend fun fetchUser(id: String): User
suspend fun updateUserAsync(user: User): Boolean
}
data class User(val id: String, val name: String)
@Test
fun `协程 mock 使用`() = runBlocking {
val mockService = mockk<AsyncUserService>()
// 定义协程方法行为
coEvery { mockService.fetchUser("123") } returns User("123", "王五")
coEvery { mockService.updateUserAsync(any()) } coAnswers {
delay(100) // 模拟异步操作
true
}
// 使用
val user = mockService.fetchUser("123")
val result = mockService.updateUserAsync(user)
// 验证协程调用
coVerify { mockService.fetchUser("123") }
coVerify { mockService.updateUserAsync(any()) }
assert(user.name == "王五")
assert(result == true)
}
}
6. 静态方法和 Object 模拟
import java.time.LocalDateTime
class StaticMockExample {
object TimeUtils {
fun getCurrentTime(): LocalDateTime = LocalDateTime.now()
fun formatTime(time: LocalDateTime): String = time.toString()
}
class EventLogger {
fun logEvent(event: String) {
val currentTime = TimeUtils.getCurrentTime()
val formattedTime = TimeUtils.formatTime(currentTime)
println("[$formattedTime] $event")
}
}
@Test
fun `模拟 object 单例`() {
val fixedTime = LocalDateTime.of(2023, 12, 25, 10, 30, 0)
mockkObject(TimeUtils)
every { TimeUtils.getCurrentTime() } returns fixedTime
every { TimeUtils.formatTime(any()) } returns "2023-12-25T10:30:00"
val logger = EventLogger()
logger.logEvent("测试事件")
verify { TimeUtils.getCurrentTime() }
verify { TimeUtils.formatTime(fixedTime) }
unmockkObject(TimeUtils)
}
}
7. 构造函数模拟
class ConstructorMockExample {
class DatabaseConnection(private val url: String) {
fun connect(): Boolean {
// 实际连接数据库的逻辑
return true
}
fun query(sql: String): List<String> {
// 实际查询逻辑
return listOf("result1", "result2")
}
}
class UserDao {
fun getUsers(): List<String> {
val connection = DatabaseConnection("jdbc://localhost:5432/test")
connection.connect()
return connection.query("SELECT * FROM users")
}
}
@Test
fun `模拟构造函数`() {
val mockConnection = mockk<DatabaseConnection>()
mockkConstructor(DatabaseConnection::class)
every { anyConstructed<DatabaseConnection>().connect() } returns true
every { anyConstructed<DatabaseConnection>().query(any()) } returns listOf("mock_user1", "mock_user2")
val userDao = UserDao()
val users = userDao.getUsers()
verify { anyConstructed<DatabaseConnection>().connect() }
verify { anyConstructed<DatabaseConnection>().query("SELECT * FROM users") }
assert(users == listOf("mock_user1", "mock_user2"))
unmockkConstructor(DatabaseConnection::class)
}
}
8. 验证选项
class VerificationExample {
interface NotificationService {
fun sendNotification(message: String)
}
@Test
fun `各种验证选项`() {
val mockService = mockk<NotificationService>()
justRun { mockService.sendNotification(any()) }
// 发送多次通知
mockService.sendNotification("消息1")
mockService.sendNotification("消息2")
mockService.sendNotification("消息1")
// 验证调用次数
verify(exactly = 2) { mockService.sendNotification("消息1") }
verify(exactly = 1) { mockService.sendNotification("消息2") }
verify(atLeast = 3) { mockService.sendNotification(any()) }
verify(atMost = 5) { mockService.sendNotification(any()) }
// 验证调用顺序
verifyOrder {
mockService.sendNotification("消息1")
mockService.sendNotification("消息2")
}
// 验证严格序列(不允许中间有其他调用)
val anotherMock = mockk<NotificationService>()
justRun { anotherMock.sendNotification(any()) }
anotherMock.sendNotification("A")
anotherMock.sendNotification("B")
verifySequence {
anotherMock.sendNotification("A")
anotherMock.sendNotification("B")
}
}
}
841

被折叠的 条评论
为什么被折叠?



