撰寫測試可以確保程式符合預期,其中單元測試擁有快速且自動化測試的優點,確保每個功能獨立運作正常,是很常見的測試類型。
Android 單元測試預設用 Junit 4,但我個人不太喜歡使用註解,加上 Kotest 是純 Kotlin 撰寫的,原生支援 Kotlin Coroutine、Kotlin DSL 炫泡寫法,所以採用 Kotest 作為單元測試框架。
單元測試著重的點是特定單元,其餘操作像是讀寫資料庫、發送 HTTP 請求都不在測試範圍內,但正常運作的程式碼會依賴這些物件,所以需要 mocking library 把這些物件模擬出來。
mocking 是模擬的意思,用於模擬特定行為,譬如模擬 HTTP 客戶端,不會真的發送請求,而是直接回傳指定的 HTTP 回應,藉此讓受測單元不須依賴其他物件。
選擇 Mockk 也是跟 Kotest 類似的理由,用 Kotlin 的我當然用 Mockk 囉。
Kotest 的 Kotlin DSL 有很多種測試格式,我偏好 BDD-style。
class RedirectViewModelTest : BehaviorSpec(), KoinTest {
override suspend fun beforeEach(testCase: TestCase) {
Dispatchers.setMain(dispatcher)
}
override suspend fun afterEach(testCase: TestCase, result: TestResult) {
Dispatchers.resetMain()
unmockkAll()
}
init {
coroutineTestScope = true
isolationMode = IsolationMode.InstancePerLeaf
Given("ID and password") {
val pref = mockk<UserPreferenceRepository>()
coEvery { pref.getCredential() } returns Credential("mock", "mock")
When("single sign-on") {
val mockMsg = "mock"
val mockUri = mockk<Uri>()
val mockService = "mock"
val repo = mockk<FcuRepository>()
Then("failed") {
val resp = SSOResponse(mockMsg, mockService, mockUri, false)
coEvery { repo.singleSignOn(any()) } returns resp
val vm = RedirectViewModel(pref, repo)
vm.fetchRedirectToken(mockService, dispatcher)
testCoroutineScheduler.advanceUntilIdle()
vm.event.value.message shouldBe mockMsg
}
Then("succeed") {
val resp = SSOResponse(mockMsg, mockService, mockUri, true)
coEvery { repo.singleSignOn(any()) } returns resp
val vm = RedirectViewModel(pref, repo)
vm.fetchRedirectToken(mockService, dispatcher)
testCoroutineScheduler.advanceUntilIdle()
vm.event.value.uri shouldBe mockUri
}
}
}
}
}