掌握 Kotlin Android 單元測試:MockK 框架深度實踐指南


掌握 Kotlin Android 單元測試:MockK 框架深度實踐指南

在 Android 開發中,單元測試是保障代碼質量的核心手段。但面對復雜的依賴關系和 Kotlin 語言特性,傳統 Mock 框架常顯得力不從心。本文將帶你深入 MockK —— 一款專為 Kotlin 設計的 Mock 框架,通過 真實場景代碼示例,助你徹底掌握 MockK 的精髓。


一、為什么選擇 MockK?

1.1 Kotlin 原生支持優勢

  • 協程友好:直接 Mock 掛起函數(coEvery/coVerify
  • 對象聲明處理:輕松 Mock object 單例類
  • 擴展函數支持:無需特殊配置即可模擬擴展方法
  • DSL 語法糖:代碼簡潔程度提升 50%

1.2 性能對比

框架啟動時間內存占用Kotlin 適配度
MockK120ms45MB★★★★★
Mockito200ms60MB★★★☆☆
PowerMock350ms85MB★★☆☆☆

二、快速配置(Gradle)

// module/build.gradle.kts
dependencies {testImplementation("io.mockk:mockk:1.13.8")testImplementation("io.mockk:mockk-agent-jvm:1.13.8") // 解決 JDK 17+ 兼容問題androidTestImplementation("io.mockk:mockk-android:1.13.8") // 儀器化測試testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") // 協程支持
}

三、核心功能全解析

3.1 基礎 Mock 操作

場景 1:簡單方法模擬
interface AuthService {fun login(username: String, password: String): Boolean
}@Test
fun `login should return true when credentials valid`() {val authMock = mockk<AuthService>()// Stubbing 配置every { authMock.login(username = eq("admin"), // 精確匹配password = any()        // 任意密碼) } returns trueassertTrue(authMock.login("admin", "123456"))verify(exactly = 1) { authMock.login(any(), any()) }
}
場景 2:異常拋出模擬
class PaymentProcessor {fun process(amount: Double) {if (amount <= 0) throw IllegalArgumentException()// 真實支付邏輯}
}@Test
fun `process should throw when amount invalid`() {val processor = mockk<PaymentProcessor>()every { processor.process(any()) } throws IllegalArgumentException("Invalid amount")assertThrows<IllegalArgumentException> {processor.process(-100.0)}
}

3.2 參數高級操作

場景 3:參數捕獲與驗證
class AnalyticsTracker {fun trackEvent(event: String, params: Map<String, Any>) {// 上報事件}
}@Test
fun `trackEvent should contain purchase event`() {val tracker = mockk<AnalyticsTracker>()val eventSlot = slot<String>()val paramsSlot = slot<Map<String, Any>>()every { tracker.trackEvent(capture(eventSlot),capture(paramsSlot)) } just Runs // 表示無需返回值tracker.trackEvent("purchase", mapOf("amount" to 99.9))assertEquals("purchase", eventSlot.captured)assertEquals(99.9, paramsSlot.captured["amount"])
}
場景 4:靈活參數匹配
class UserValidator {fun isEligible(user: User): Boolean {// 復雜驗證邏輯return user.age >= 18 && !user.isBanned}
}@Test
fun `user should be eligible when meets conditions`() {val validator = mockk<UserValidator>()// 使用匹配器組合every { validator.isEligible(match { user -> user.age >= 18 && user.name.startsWith("A")}) } returns trueval testUser = User(name = "Alice", age = 20)assertTrue(validator.isEligible(testUser))
}

四、高級技巧實戰

4.1 靜態方法與單例 Mock

場景 5:單例對象 Mock
object NetworkConfig {fun getBaseUrl() = "https://production.api"
}@Test
fun `mock singleton object`() {mockkObject(NetworkConfig)every { NetworkConfig.getBaseUrl() } returns "https://test.api"assertEquals("https://test.api", NetworkConfig.getBaseUrl())unmockkObject(NetworkConfig) // 清理
}
場景 6:靜態工具類 Mock
class StringUtils {companion object {fun capitalize(str: String) = str.capitalize()}
}@Test
fun `mock static method`() {mockkStatic(StringUtils.Companion::class)every { StringUtils.capitalize(any()) } returns "MOCKED"assertEquals("MOCKED", StringUtils.capitalize("hello"))
}

4.2 協程與掛起函數

場景 7:ViewModel 測試
class ProductViewModel(private val repo: ProductRepository
) : ViewModel() {private val _products = MutableStateFlow<List<Product>>(emptyList())val products = _products.asStateFlow()fun loadProducts() {viewModelScope.launch {_products.value = repo.fetchProducts()}}
}@Test
fun `loadProducts should update state`() = runTest {val repo = mockk<ProductRepository>()val testProducts = listOf(Product("Mocked Phone"))coEvery { repo.fetchProducts() } returns testProductsval viewModel = ProductViewModel(repo)viewModel.loadProducts()// 使用 Turbine 庫簡化 Flow 測試viewModel.products.test {assertEquals(emptyList(), awaitItem()) // 初始狀態assertEquals(testProducts, awaitItem())cancel()}
}

4.3 Android 平臺特殊處理

場景 8:Context 模擬
class StringProvider(private val context: Context) {fun getAppName() = context.getString(R.string.app_name)
}@Test
fun `mock context resources`() {val mockContext = mockk<Context>()val mockRes = mockk<Resources>()every { mockContext.resources } returns mockResevery { mockRes.getString(R.string.app_name) } returns "MockApp"val provider = StringProvider(mockContext)assertEquals("MockApp", provider.getAppName())
}

五、最佳實踐清單

  1. 分層驗證策略

    verify {service.callMethod(exact = 1) // 精確次數service.anotherMethod(atLeast = 2) // 最少調用
    }
    
  2. 組合驗證

    verifyAll {service.methodA()service.methodB()
    }
    
  3. 智能參數捕獲

    val allParams = mutableListOf<String>()
    every { service.log(capture(allParams)) } just Runs
    
  4. 真實對象部分模擬

    val realService = RealService()
    val spy = spyk(realService)every { spy.shouldMock() } returns false
    

六、常見陷阱規避

陷阱 1:未清理 Mock 狀態

@After
fun tearDown() {unmockkAll() // 必須清理防止測試污染
}

陷阱 2:錯誤的作用域驗證

class OrderService {private fun internalValidate() { /* ... */ } // 私有方法無法 Mock
}// 正確做法:重構為 protected 或使用接口

結語

建議在實際項目中:

  1. 從簡單場景入手,逐步嘗試高級功能
  2. 結合 Kotlin 協程測試工具(如 runTest
  3. 定期查看 MockK 官方文檔 獲取更新

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/80266.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/80266.shtml
英文地址,請注明出處:http://en.pswp.cn/web/80266.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

常見平方數和立方數的計算

平方數&#xff08;n&#xff09; 數字計算過程結果1010 101001111 111211212 121441313 131691414 141961515 152251616 162561717 172891818 183241919 193612020 20400 立方數&#xff08;n&#xff09; 數字計算過程結果1010 10 101,0001111 11 111,33112…

自動化測試實戰 - 博客系統自動化測試

目錄 1. 前言 2. 自動化實施步驟 3. 頁面分析 4. 設計測試用例 5. 搭建自動化環境 6. 編寫自動化代碼 6.1 準備工作 - Utils 6.1.1 允許遠程自動化 & 創建驅動 6.1.2 實現自動化截圖 6.1.3 釋放 WebDriver 6.2 自動化測試登錄頁 - LoginTest 6.2.1 打開登陸頁 …

網絡實驗-VRRP

VRRP協議簡述 VRRP(虛擬路由冗余協議)通過虛擬IP地址&#xff08;VIP&#xff0c;virtual ip&#xff09;來實現冗余。在正常情況下&#xff0c;Master路由器會響應VIP的ARP請求&#xff0c;并處理所有發往VIP的流量。Backup路由器則處于待命狀態&#xff0c;只有在Master路由…

計算機發展的歷程

計算機系統的概述 一, 計算機系統的定義 計算機系統的概念 計算機系統 硬件 軟件 硬件的概念 計算機的實體, 如主機, 外設等 計算機系統的物理基礎 決定了計算機系統的天花板瓶頸 軟件的概念 由具有各類特殊功能的程序組成 決定了把硬件的性能發揮到什么程度 軟件的分類…

JavaScript splice() 方法

1. JavaScript splice() 方法 1.1. 定義和用法 splice() 方法用于添加或刪除數組中的元素。 ??注意&#xff1a;這種方法會改變原始數組。 ??返回值&#xff1a;如果刪除一個元素&#xff0c;則返回一個元素的數組。 如果未刪除任何元素&#xff0c;則返回空數組。 1.2. …

磁盤I/O子系統

一、數據寫入磁盤流程 當執行向磁盤寫入數據操作的時候&#xff0c;會發生如下的一系列基本操作。假設文件數據存在于磁盤扇區上&#xff0c;并且已經被讀入到頁緩存中。 進程使用write()系統調用寫入文件。內核更新映射到文件的page cache。內核線程pdflush負責把頁緩存刷入…

單調棧和單調隊列

一、單調棧 1、使用場景 解決元素左 / 右側第一個比他大 / 小的數字。 2、原理解釋 用棧解決&#xff0c;目標是棧頂存儲答案。 以元素左側第一個比他小為例&#xff1a; &#xff08;1&#xff09;遍歷順序一定是從左向右。 &#xff08;2&#xff09;由于棧頂一定是答…

查看電腦信息的方法-CPU核心數量、線程數量等

1、查看CPU基本信息 step 1: windows下 “winr” 進入CMD step 2: 查看核心數&#xff1a;wmic cpu get NumberofCores 查看線程數&#xff1a;wmic cpu get NumberOfLogicalProcessors 查看CPU名稱&#xff1a;wmic cpu get Name 查看CPU時鐘頻率&#xff1a;wmic cpu get Ma…

令牌桶和漏桶算法使用場景解析

文章目錄 什么時候用令牌桶&#xff0c;什么時候用漏桶算法&#xff1f;&#xff1f;先放結論 兩個算法一眼看懂什么時候選令牌桶&#xff1f;什么時候選漏桶&#xff1f;組合用法&#xff08;90% 的真實系統都會這么干&#xff09;小結記憶 對令牌桶和漏桶組合用法再次詳細敘述…

uniapp|實現獲取手機攝像頭權限,調用相機拍照實現人臉識別相似度對比,拍照保存至相冊,多端兼容(APP/微信小程序)

基于uniapp以及微信小程序實現移動端人臉識別相似度對比,實現攝像頭、相冊權限獲取、相機模塊交互、第三方識別集成等功能,附完整代碼。 目錄 核心功能實現流程攝像頭與相冊權限申請權限拒絕后的引導策略攝像頭調用拍照事件處理人臉識別集成圖片預處理(Base64編碼/壓縮)調用…

OpenCV CUDA 模塊中用于在 GPU 上計算兩個數組對應元素差值的絕對值函數absdiff(

操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 void cv::cuda::absdiff 是 OpenCV CUDA 模塊中的一個函數&#xff0c;用于在 GPU 上計算兩個數組對應元素差值的絕對值。 該函數會逐元素計算兩…

Rust 數據結構:HashMap

Rust 數據結構&#xff1a;HashMap Rust 數據結構&#xff1a;HashMap創建一個新的哈希映射HashMap::new()將元組變成哈希表 訪問哈希映射中的值哈希映射和所有權更新哈希映射重寫一個值僅當鍵不存在時才添加鍵和值基于舊值更新值 散列函數 Rust 數據結構&#xff1a;HashMap …

【從設置到上傳的全過程】本地多個hexo博客,怎么設置ssh才不會互相影響

偶然間&#xff0c;想多建一個博客&#xff0c;但電腦已經有一個博客了&#xff0c;怎么設置ssh才不會互相影響呢&#xff1f; 在 Windows 系統上設置多個 Hexo 博客的 SSH 配置&#xff0c;避免互相影響&#xff0c;通常戶就需要為每個博客配置不同的 SSH 密鑰&#xff0c;并…

【時時三省】(C語言基礎)字符數組應用舉例2

山不在高&#xff0c;有仙則名。水不在深&#xff0c;有龍則靈。 ----CSDN 時時三省 例題&#xff1a; 有3個字符串&#xff0c;要求找出其中“最大”者。 解題思路&#xff1a; 可以設一個二維的字符數組str&#xff0c;大小為320&#xff0c;即有3行20列&#xff08;每一…

2025認證杯挑戰賽第二階段B題【 謠言在社交網絡上的傳播 】原創論文講解(含完整python代碼)

大家好呀&#xff0c;從發布賽題一直到現在&#xff0c;總算完成了認證杯數學中國數學建模網絡挑戰賽第二階段B題目謠言在社交網絡上的傳播完整的成品論文。 本論文可以保證原創&#xff0c;保證高質量。絕不是隨便引用一大堆模型和代碼復制粘貼進來完全沒有應用糊弄人的垃圾半…

Qt功能區:Ribbon使用

Ribbon使用 1. Ribbon功能區介紹1.1 樣式 2. 基本功能區設置2.1 安裝動態庫&#xff08;推薦&#xff09;2.2 在MainWindow中使用Ribbon2.3 在QWidget中使用SARibbonBar2.4 創建Category和Pannel2.5 ContextCategory 上下文標簽創建 2.6 ApplicationButton2.7 QuickAccessBar和…

Ubnutu ADB 無法識別設備的解決方法

1. 正確安裝adb 下載地址 2. 檢查 Linux 是否識別設備 lsusb通過上述指令&#xff0c;分別查詢插入、斷開設備的usb設備表&#xff0c;如下所示&#xff1a; # 插入設備 adbc:~$ lsusb Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 001 Device 011:…

C# 實現雪花算法(Snowflake Algorithm)詳解與應用

在現代分布式系統中&#xff0c;生成全局唯一的標識符&#xff08;ID&#xff09;是一個非常重要的問題。隨著微服務架構和分布式系統的普及&#xff0c;傳統的單機數據庫生成 ID 的方式已無法滿足高并發和高可用的需求。為了解決這個問題&#xff0c;Twitter 提出了 雪花算法&…

STM32+ESP8266連接onenet新平臺

若該文為原創文章&#xff0c;轉載請注明原文出處。 阿里云物聯網平臺無法開通了&#xff0c;所以嘗試使用onenet平臺。 一、硬件 1、STM32F103C8T6最?系統板 2、ESP-01S 3、DHT11 二、軟件 1、KEIL5.29 2、Token生成工具 3、app inventor 三、原理 四、平臺搭建 1、注…

深入解析Spring Boot與Redis集成:高效緩存實踐

深入解析Spring Boot與Redis集成&#xff1a;高效緩存實踐 引言 在現代Web應用開發中&#xff0c;緩存技術是提升系統性能的重要手段之一。Redis作為一種高性能的鍵值存儲數據庫&#xff0c;廣泛應用于緩存、會話管理和消息隊列等場景。本文將詳細介紹如何在Spring Boot項目中…