MockK:一款強大的 Kotlin Mocking Library (Part 2 / 4)

MockK 功能介紹:mockk, every, Annotation, verify

Joe Tsai
Joe Tsai
Sep 19, 2018 · 5 min read
Image for post
Image for post

前情提要

在 Java 的世界裡面,說到單元測試,不得不提到 Mockito,它是目前 Java 最多人使用的 Mocking framework,但若來到 Kotlin 的世界呢?我們還能仰賴 Mockito 進行單元測試嗎?有沒有專門為 Kotlin 這款語言量身打造的 Mocking framework 呢?

這系列文章會手把手教你如何使用 MockK,讓你了解它好用的地方在哪裡?以及它解決了什麼樣的問題?一共分成 4 篇文章,包括以下內容:

  1. 用 Kotlin + Mockito 寫單元測試會碰到什麼問題?
  2. MockK 功能介紹:mockk, every, Annotation, verify(這篇)
  3. MockK 功能介紹:Relaxed Mocks, 再談 Verify , Capture
  4. 如何測試 Static Method, Singleton

看完前一篇文章提到的問題,心裡不免 OS:「只是想用 Kotlin 寫單元測試而已,有必要這麼折磨人嗎?」況且我連測試都還沒開始寫耶!如果可以不用處理這些問題又可以測試靜態方法,該有多好?

MockK

MockK 是一款專門為 Kotlin 所設計的 Mocking Library,Mockito 能做到的事情它都做得到,Mockito 做不到的事情它也做得到,簡而言之 MockK 可以讓你無痛地在 Kotlin 下使用 Mockito 跟 PowerMock 的功能,它是一款完全獨立的套件,你不會需要引入 Mockito 或 PowerMock,更不會被兼容性的問題綁架。

Gradle Setting

首先,在 build.gradle(app) 加入以下設定:

dependencies {    testImplementation 'io.mockk:mockk:1.8.6'    ...}

Example: 小孩要零用錢

為方便講解,這邊會用小孩要零用錢這個案例來進行說明,每個小孩都會需要零用錢,通常小孩沒錢時就會跟媽媽要零用錢,於是我宣告了一個叫 Kid 的 Class:

Kid 的建構子會傳入 Mother 這個 Class,每一個小孩初始情況是沒有錢的,小孩有一個功能就是要零用錢 wantMoney(),每當他要零用錢時,媽媽就會呼叫 giveMoney() 這個方法給小孩零用錢。

而 Mother 這個 Class 很單純:

只要小孩跟媽媽要錢,媽媽就給他 100 元。

mockk, every

接著對 wantMoney() 進行測試,測試代碼如下:

首先,我們 Mock Mother 這個 Class:

val mother = mockk<Mother>()

在 MockK 想要 Mock 一個 Class 的時候,只要使用 mockk<Class>()即可,後面泛型填入想要 Mock 的 Class 。

接著 New 了一個 Kid 物件:

val kid = Kid(mother)

並用到了 every 這個方法:

every { mother.giveMoney() } returns 30

這邊的 every 你可以把它想成是 Mockito 的 when().thenReturn(),每當有人呼叫 mother.giveMoney() 這個方法,我就給他 30 元。也就是說,你改變了這個方法行為 ── 指定它要做什麼事情。

接著執行測試動作,並驗證小孩的錢是否改變:

// When
kid.wantMoney()
// Then
assertEquals(30, kid.money)

最後,小孩的錢確實被改成 30 元,測試通過!

Annotation

MockK 跟 Mockito 一樣,也支援 Annotation 的方式進行初始化:

只要在 @Before 方法裡面加上 MockKAnnotations.init(this) 就能在外面用 Annotation 的方式 Mock 物件。

需求變更:必須通知媽媽

萬一媽媽現在變得很嚴格,規定小孩每次要零用錢的時候,都要先通知媽媽才行,於是我們修改一下 Kid Class,在 wantMoney() 中加上 mother.inform(money)

Mother Class 則新增了 inform() 這個方法:

實作內容很簡單,就是印出 “媽媽我現在有 XX 元,我要跟你拿錢!” 而已。

Verify

針對上面的情況來說,我們要確保的是:小孩要零用錢的時候,同時也有通知媽媽,於是將測試代碼改成如下:

這邊只在倒數第二行加上:

verify { mother.inform(any()) }

當你想要驗證一段方法有沒有被呼叫到的時候可以使用 verify

MockKException: No answer found for: Mother(#1).inform(0)

接著運行一下測試代碼,你會得到這樣的訊息:

io.mockk.MockKException: no answer found for: Mother(#1).inform(0)

這是因為在 MockK 裡面,預設情況下 Mock 這個動作是很嚴謹的,你必須要指定所有的行為操作才行,在測試代碼前面加上這行:

every { mother.inform(any()) } just Runs

再次運行測試,通過!

上面這種做法隱藏著一個問題:「假設今天 Class 的方法有 100 個,那豈不是要指定到天荒地老了嗎?」有沒有辦法跟 Mockito 一樣不用指定行為也能做後續的 verify

下篇文章將會介紹如何解決這個問題:

Joe Blog

想到什麼寫什麼

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store