通用
assert:用于条件验证,verify:验证方法是否被调用、调用次数、顺序、参数等
answers:mock函数体,coAnswers:mock协程函数体
returns:mock函数的返回值,returnsMany:函数多次调用时按顺序返回多个不同值
throws:mock异常
遇到的坑!!!
坑一:多次调用 mockkStatic(...) 同一类,后写的会覆盖前面写的
// 工具类 Util.kt,定义了两个顶层函数
fun getProductList():{}
fun getTitle():{}
// 代码示例
mockkStatic("com.example.app.UtilKt")
every { Util.getProductList() } answers { println("被mock了") }
mockkStatic(::getTitle) // ❌ 这个mock函数会覆盖上面的mock,导致上面的mock失效
every { getTitle() } answers { println("被mock了") }
✅正确的写法是:如果一个类中有多个函数需要mock,应该用“包名”的写法
mockkStatic("com.example.app.UtilKt")
every { Util.getProductList() } answers { println("被mock了") }
every { Util.getTitle() } answers { println("被mock了") }
坑2:public属性应该直接赋值,不能用every方法mock
MockK的主要功能是拦截和控制模拟对象的方法调用以及属性的访问(即 getter 和 setter 的行为)。MockK 并没有为公共字段(public)的行为添加额外的拦截逻辑,而是保留了直接操作字段的能力
如果我们尝试用 MockK 的 every 语法来模拟公共字段,比如:
mockk<PackageInfo>(relaxed = true) {
every { packageName } returns appPkgName
}
会抛出异常(例如 “Missing mocked calls inside every { ... } block”)。这是因为 every 是设计用来模拟方法调用或属性 getter/setter 行为的,而 packageName 是一个字段,不是方法或属性。MockK 无法拦截对字段的直接访问,因此这种写法是无效的。
✅正确的写法应该直接赋值:
mockk<PackageInfo>(relaxed = true) {
packageName = appPkgName
}
mockk<T>():创建接口/类的 mock 实例
class Person() {
var name: String = ""
var age: Int = 0
}
// 1. relaxed = true 代表所有属性用默认值。否则,每个属性你都要手动mock。如果访问未mock的属性或函数时,会抛异常
val person = mockk<Person>(relaxed = true)
// 2. mock时自定义属性的值
val person = mockk<Person>(relaxed = true)
every { person.name } returns "hello"
every { person.age } returns 18
// 3. 也可以用下面这种写法
val person = mockk<Person>(relaxed = true) {
every { name } returns "hello"
every { age } returns 18
}
mockkObject:用于 Kotlin object 单例
假设我们有这样一个对象:
// File: Utils.kt
object Utils {
fun getCurrentTime(): Long = System.currentTimeMillis()
fun greet(name: String): String = "Hello, $name"
}
单元测试中 mock 它:
import io.mockk.*
import kotlin.test.*
class UtilsTest {
@BeforeTest
fun setUp() {
mockkObject(Utils) // ✅ mock 单例对象
}
@AfterTest
fun tearDown() {
unmockkObject(Utils) // ✅ 清理 mock,防止污染其他测试
}
@Test
fun testMockObjectFunction() {
every { Utils.getCurrentTime() } returns 123456789L
every { Utils.greet("Alice") } returns "Hi, Alice"
assertEquals(123456789L, Utils.getCurrentTime())
assertEquals("Hi, Alice", Utils.greet("Alice"))
verify {
Utils.getCurrentTime()
Utils.greet("Alice")
}
}
}
mockkStatic:针对顶层/静态方法进行 mock
kt文件-顶层函数
如果你要 mock 的是顶层函数,最简洁的方式是直接传入函数引用
假设在 com.example.HelloWorld.kt 文件中有:
package com.example
fun sayHello(): String = "Hello Kotlin!"
mock方式:
import com.example.sayHello // 导入顶层函数
import io.mockk.every
import io.mockk.mockkStatic
import org.junit.Test
import kotlin.test.assertEquals
class HelloWorldTest {
@Test
fun `mock sayHello via function reference`() {
// 使用函数引用
mockkStatic(::sayHello)
every { sayHello() } returns "Mocked Hello"
assertEquals("Mocked Hello", sayHello())
}
}
mockkStatic(::sayHello)
会把顶层函数sayHello
变为可mock的静态方法
后续再对同名函数的调用,均走 stub 逻辑,而不执行原始实现。
kt文件-顶层扩展函数
在 com/example/StringExt.kt 文件中, 定义了一个String的扩展函数
package com.example
fun String.starSurround(): String = "*$this*"
要 mock 顶层扩展函数,你不能这么写:
mockkStatic(::starSurround) // ❌ 不行!
正确写法:
mockkStatic("com.example.StringExtKt") // 传入编译后生成的 Java 类名(文件名 + Kt)
every { any<String>().starSurround() } returns "#hello#"
assertEquals("hello".startSurround(), "#hello#")