Android 寫測試系列 (1) — MockK 介紹

黃德銘
Dcard Tech Blog
Published in
5 min readApr 8, 2022

以前介紹過 Android 的測試 Framework Kotest,接著想介紹如何利用 Kotest 撰寫不同類型的測試,其中包含一般物件、Singleton 物件、Coroutine 非同步以及 ViewModel,會由淺入深從 mock 物件開始到搭配一些情境來撰寫測試,撰寫方式大多是我自己摸索出來的,有更好的寫法也歡迎大家討論交流。

現在常見的 MVVM 架構可以拆分為 View — ViewModel — Model,ViewModel 為了完成來自 View 的 request,會需要跟 Model 拿需要的資料,而不管是透過網路或是從手機拿資料都有失敗的可能性,如果要針對商業邏輯做測試,我們就不會希望因為突發性事件而影響 ViewModel 的測試結果,所以我們可以 “模擬” 外部的物件,也稱為 mock,這篇就會介紹如何使用 MockK 來 mock 物件,以及有哪些常用功能。

現在定義 Car

那我們來對 Car 寫些測試,要建立一個 mock 物件時我們可以這樣產生

其中的 T 代表該物件的 type,然後我們用 Kotest 寫一條測試

附帶一提:從 Android Studio 執行 Kotest 的測試會需要安裝 Plugin

在執行完上面的測試後,會看到 Console 有這個錯誤

因為 Car 的物件是假的物件,當別人跟它拿資料時我們必須處理該給什麼資料,所以我們可以透過 everycoEvery 處理。coEvery 是用來處理 suspend function,在 MockK 中看到 co 開頭的都是跟 Coroutine 有關,以後會常常看到。

然後我們就能把測試調整成這樣

在寫測試時有時候我們會希望該物件的部分邏輯真的能夠執行,如果我們 mock 了該物件,那些重要邏輯就得複製到測試中,這實在是太不切實際,所以對比於 mock 的全模擬,我們有另一個選擇是 spyk,我們來舉個例子

我們可以把一個真實的物件丟給 spy 物件,當我們沒有指定 spy 物件該怎麼做時,就會照原本物件的設定動作;而有指定時就會照我們的設定而動作,因此會看到兩個 spy 物件執行 drive() 卻得到不同的結果。另外也看到用了 verify 來做驗證,verify 主要用途是確認某個動作的執行次數,常用情況有

exactly 為確切次數,atLeast 為最少次數,atMost 為最多次數

有 verify 就能幫助我們確認是不是有邏輯漏掉了,這是一定要會學起來的。另外也可以把 mock 物件丟給 spy 物件,也許某些較為困難的測試會變得簡單一些 (* ̄▽ ̄)/‧★*”`’*-.,_,.-*’`”*-.,_☆,.-*’`”*-.,_

在寫測試時我們會針對不同情況而有不同的測試資料,所以在設定 mock 物件時可以用多個 every 來達成這個需求

allAny() 代表該 type 的所有資料,用在 drive(location: String) 代表參數給任何字串都可以符合這條,所以參數除了 Taipei 和 America 外其他都會回傳 Unknown

當支援的 location 變多,就要再增加 every 的數量,可能就會增加撰寫測試上的麻煩,我們可以利用捕捉參數的功能來化簡成一個 every 就好

在之前的測試中我們都是用 returns 搭配 every,當邏輯變得複雜時,可以改用 answers { doSomething() } 來處理回傳的資料

前面有提到

因為 Car 的物件是假的物件,當別人跟它拿資料時我們必須處理該給什麼資料,所以我們可以透過 every 和 coEvery 處理。

但有些 function 是不用回傳值,如果我們每個 function 都要透過 every 設定動作(例:returns Unit),可能測試還沒寫完就先累死,MockK 提供了 2 種方式來處理這個問題

  • mockk(relaxed = true)
    這個會將未設定 every 的 function 給個預設回傳值,例如 null , 空字串, Unit…等,但我自己不大推薦使用這個,在產品不斷地發展之下,新舊邏輯衝突是很常見的,當不小心漏掉 every 時,不同的邏輯在使用不同的參數 call 同樣的 function 拿到同樣的 null,讓你最後跑出來的測試結果可能會相同,等到新功能上線後才發現出事啦阿北就慘了,所以寧願在寫測試時多花一些時間寫 every 也不要出事之後寫檢討報告 😢,還是減少使用吧!
  • mockk(relaxUnitFun = true)
    這個跟上面的有點類似,但是只針對沒有回傳值(return Unit) 的 function 預設 return Unit,一般來說沒回傳值的 function 重點在於其實作內容,但 mock 物件直接讓你的實作內容變沒用,所以直接幫你省掉時間寫 every { car.xxx() } returns Unit 就變得有用非常多,懶人就可以使用這個設定。

這次介紹的都是我覺得重要的基本功能,未來用到 MockK 其他功能時會一併介紹。下一篇會介紹 BDD(Behavior-driven development) 以及設定一個情境,將一段程式碼修改成容易寫測試的狀態並利用 DescribeSpec 撰寫測試。

最後幫大家整理出 3 個重點

相關連結:

--

--

黃德銘
Dcard Tech Blog

愛東玩西玩的 Android 工程師,玩過 Ktor, Jetpack Compose for Desktop 和 iOS