IP地址检测
视频播放器
输入关键词搜索
登录
Android单元测试

Android单元测试

2025-05-15
暂无分类

通用

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#")
THE END
0/500
暂无评论