Keyword: Coroutine mock
直到27日,完成KMM的測試功能放在
KMMDay27
今天開始要來寫KMM的測試,由於我們使用DB或是進行網路請求都是suspend function,所以需要先撰寫一個給測試使用的基礎Coroutine方便我們進行測試.
首先,在CommonTest的專案底下,建立一個expect物件,BaseTest,裡面只有一個runTest的方法.
//這是在commonTest底下的Kotlin
import kotlinx.coroutines.CoroutineScope
expect abstract class BaseTest() {
fun <T> runTest(block: suspend CoroutineScope.() -> T)
}
有了expect當然就需要去雙平台實作actual讓雙平台在測試時候有辦法使用,跟commonMain的expect物件一樣.必須有同樣的packagePath
Android的實作方法如下,其中的coroutineTestRule是我們自己寫的Rule,他會在測試開始與結束時重設Dispather,避免測試的順序影響到測試的結果..
// 這是在androidTest 底下的Kotlin
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking
import org.junit.Rule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
actual abstract class BaseTest {
@get:Rule
var coroutineTestRule = CoroutineTestRule()
actual fun <T> runTest(block: suspend CoroutineScope.() -> T) {
runBlocking { block() }
}
}
CouroutineTestRule如下,非常簡單的結構
// 這是在androidTest 底下的Kotlin
class CoroutineTestRule(
private val testDispatcher: ExecutorCoroutineDispatcher = Executors.newSingleThreadExecutor()
.asCoroutineDispatcher()
) : TestWatcher() {
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(testDispatcher)//設定Main
}
override fun finished(description: Description?) {
super.finished(description)
Dispatchers.resetMain()//重設Main
}
}
這邊我們有用到junit等等的常用測試工具,所以要在gradle(shared)的androidTest部分加上他們的引用
//這是在build.gradle.kts中的Kotlin
...
val androidTest by getting {
dependencies {
...
implementation(Develop.KotlinTest.jvm)
implementation(Develop.KotlinTest.junit)
implementation(Develop.AndroidXTest.core)
implementation(Develop.AndroidXTest.junit)
implementation(Develop.AndroidXTest.runner)
implementation(Develop.AndroidXTest.rules)
...
}
}
...
//這是在buildSrc內的Kotlin設定
Develop{
object AndroidXTest {
val core = "androidx.test:core:${Versions.AndroidX.test}"
val junit = "androidx.test.ext:junit:${Versions.AndroidX.test_ext}"
val runner = "androidx.test:runner:${Versions.AndroidX.test}"
val rules = "androidx.test:rules:${Versions.AndroidX.test}"
}
object KotlinTest {
val common = "org.jetbrains.kotlin:kotlin-test-common:${Versions.kotlin}"
val annotations = "org.jetbrains.kotlin:kotlin-test-annotations-common:${Versions.kotlin}"
val jvm = "org.jetbrains.kotlin:kotlin-test:${Versions.kotlin}"
val junit = "org.jetbrains.kotlin:kotlin-test-junit:${Versions.kotlin}"
}
}
Versions{
val kotlin = "1.5.21"
}
這樣我們就準備好Android的測試Coroutine了,接下來再去實作iOS使用的測試Coroutine
同樣的,在同樣的package path下,建立與expect同名的actual實作
//這是在iosTest底下的Kotlin語言
actual abstract class BaseTest {
@OptIn(DelicateCoroutinesApi::class)
actual fun <T> runTest(block: suspend CoroutineScope.() -> T) {
var error: Throwable? = null
//由於iOS沒有Coroutine環境,所以改成GlobalScope來進行測試
GlobalScope.launch(Dispatchers.Main) {
try {
block()
} catch (t: Throwable) {
error = t
} finally {
CFRunLoopStop(CFRunLoopGetCurrent())//這邊是由Native提供的iOS方法
}
}
CFRunLoopRun()//這邊是由Native提供的iOS方法
error?.also { throw it }
}
}
有了這兩個基礎的測試後,就有coroutine環境可以進行更進一步的測試