上一篇有用MVP模式提到Mock的概念,Mock是什麼呢?你有沒有發現我們在測試Presenter的時候除了被測試的程式,還有一些外部相依的物件需要被呼叫。在MainActivityPresenter的requestUserName()裡面除了Server()直接在function內部呼叫物件實體外,另外會用到的IView物件是由Constructor用Setter方式注入,如果我們在實體化Presenter的時候沒有帶入IView就會在執行時發生Nullpointer的錯誤。
正常情況在launch app時候、MainActivity執行時我們有把this指標放入Presenter裡,但是在單元測試裡沒有launch app去執行MainActivity那該怎麼把IView放入單元測試的Presenter中呢?
這時你可能會想到很直覺的解決方法
val view = IView()
val presenter = MainActivityPresenter(view)
//or
val view = MainActivity()
val presenter = MainActivityPresenter(view)
結果是大失所望,因為IView是Interface在Kotlin或是Java都無法直接實體化interface,那我們轉為實體化MainActivity一執行馬上發生錯誤,因為你沒有launch app無法執行MainActivity的生命週期。難不成我們的測試就做不下去了,當然不會,我們就測試目的Mock(模擬,仿造)一個有Activity的外殼又不是真的Activity的物件就行了,這要怎麼做呢?
class MockMainActivity : IView {
override fun receivedUserName(name: String) {
//only for mock, not implement ui here
}
}
但為了讓大家熟悉Mock的觀念,我們先不要靠Library自己來手動實作Mock功能,
我們可以寫一個MockMainActivity的class一樣實作IView介面,然後receivedUserName留空,請記得我們的測試只是要驗證後receivedUserName()要被呼叫到一次。
class ExampleUnitTest {
@Test
fun testRequestUserName() {
val view = MockMainActivity() //剛剛是用mockk<IView>
val presenter = MainActivityPresenter(view)
every {
view.receivedUserName(any())
} just Runs
presenter.requestUserName()
verify {
view.receivedUserName(any())
}
}
}
回到我們在上一章MVP的測試程式,最後把原本使用Mockk的IView改成MockMainActivity注入Presenter就OK了,測試後跟使用mockk一樣Test passed,這樣我們就手動完成Mock一個MainActivity物件。這樣看起其實Mock觀念一點也不難,我們透過DIY也可以做出來,Mock就是模仿一個物件。但試想如果今天有上百上千個Mock object要寫,那你的Testing code要變的多可怕多難維護,每做一個Test case就多一堆測試專用的class。比較好的方法還是用專門的Mocking framework像是Mockito或是Mockk,透過reflection的方式在runtime產生測試物件這是比較好的選擇。
在單元測試裡其實有一些比較嚴謹的定義如,stub,mock,spy,dummy等等,但我們實際使用這些mocking framework來操作時並沒有這麼細分,以mockk為例它提供我們一個很強大的mock類別操作,端看你怎麼使用,你只用stub的功能它就是stub,你用到mock的功能它就是mock,因為萬變不離其宗就是不同程度的仿照而已。所以我這邊先不對每個定義做很詳細的解釋,因為我們實際的操作大部份都是mock,所以我的介紹就用mock來統稱這些行為(因為Mockk跟Mockito是也用mock來命名嘛)。
下一章我們會繼續講到Mockk的使用方法。