在Day 5裡我們介紹了mock的一些基本觀念及DIY實作,但是真實世界不會有人都用DIY的方式來寫mock object,在mocking framework的幫助下我們可以很方便的實作我們的測試mock object。那mockk又是何方神聖呢?
早在Mockk出現前在JVM平台上比較受歡迎的單元測試框架是Mockito,它帶有強大的mock功能,可以仿造許多自定object的行為而不用像我在Day 5 Mock object中的文章提到,要改變mock object的細部行為還得自己寫mocking class,但Mockito在Android使用上有很多限制不再贅述,在Android + Kotlin的開發上Mockk改善了許多Mockito的缺點比較適合使用。
Mockk提供了很方便的映射功能,可以完全仿照物件的行為,讓你在測試時候可以快速製造物件內function或field的各種回傳值或行為,以我們在MVP章節為例,
class ExampleUnitTest {
@Test
fun testRequestUserName() {
val view = mockk<IView>()
val presenter = MainActivityPresenter(view)
every {
view.receivedUserName(any())
} just Runs
presenter.requestUserName()
verify {
view.receivedUserName(any())
}
}
}
我們看到了第一個跟mockk有關的部份是mockk<IView>()。
val view = mockk<IView>()
這是mockk用映射的方式來覆寫IView裡的狀態,可以動態指定IView裡的function或是field回傳值,但這個時間點我們還沒有假造任何狀態,只是把有能力假造IView狀態的這個物件指給view這個變數。
every {
view.receivedUserName(any())
} just Runs
every指的是我們要假造IView的狀態,一旦遇到區塊裡的情況我們要做什麼事。我們的程式碼要假造的狀況是當每次IView被呼叫receivedUserName()的時候。但我們只是要驗證receivedUserName()有被呼叫所以在區塊後面接上
just Runs,這是告訴mockk我們只要紀錄receivedUserName被呼叫的狀態而不用管它的回傳值。any()是mockk裡的dummy物件,意指給一個任何型別的空物件,因為我們的測試不在意傳入的參數,function參數跟我們的測試無關只是為了符合interface的規定而已。
第一次接觸mock的人會覺得很莫名其妙,我們把IView mock之後的實體view放入presenter裡,presenter本來就會呼叫內部程式,view.recivedUserName()本來就會被呼叫呀!我今天在這裡用every區塊定義它的原因是什麼?有兩個原因。
every {
view.receivedUserName(any())
} returns ("Daniel Chen")
verify {
view.receivedUserName(any())
}
verify顧名思義就是驗證,用來驗證我們的被測程式(presenter)在執行時,mock物件是否如我們的預期一樣有做verify區塊內的行為。verify區塊不是必要的,如果你的驗證沒有mock物件參與或跟mock物件無關時,可能只需要用AssertEqual之類的值比對方式來做驗證,例如[Day 2]舉例的add function並不需要驗證mock物件,那自然也不用呼叫verify。但如果今天你是測試Android的MVP時那verify是一定會用到的東西。
回到[Day 4]MVP的章節,我們預期的是當presenter被呼叫requestUserName()的時候presenter裡面的view.receivedUserName()這行程式會被呼叫到。所以當我們真的去執行presenter時,如果執行到verify的區塊時,view.receivedUserName()被呼叫到測試程式就會通過,沒有呼叫到測試程式就會失敗。
我們先簡單的介紹我們在MVP的範例用到的mockk範例與mocking framework的使用觀念,下一節會繼續介紹mockk的一些常用的進階用法。