mockk使用指南

欢迎访问我的主页: 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")
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值