Activity 的測試基本上寫法與 Fragment 相似,但要完成 Activity 的測試會馬上面臨一些問題:
AsyncTask
支援的比較完善,所以在自定義的 Background thread 執行的程式 (Executor、RxJava、Coroutines) ,必須要想辦法把程式切換成同步執行如果不處理這些問題,很大機率會碰上 NoMatchingViewException
,其原因就是上面幾點造成 Espresso 來不及更新 UI 導致。
因此 Espresso 提供 Idling Resources 來通知 Espresso 當前 App 是否處於忙碌中。
關於 Idling Resources 不會在今天介紹太多,可能會在後面還有多餘的時間時才會深入探討。由於 Activity 的測試需要與多個頁面連動,在還沒有把程式寫完的情況下我今天會以寫範例的性質寫測試。
基本上這個階段與建立 FragmentTest 時雷同,不過還要再加入 Idling Resources。
先創建一個基本的 Sample IdlingResource :
class SimpleIdlingResource(private val resourceName: String) : IdlingResource {
private val isIdle = AtomicBoolean(true)
@Volatile private var resourceCallback: IdlingResource.ResourceCallback? = null
override fun getName(): String = resourceName
override fun isIdleNow(): Boolean = isIdle.get()
override fun registerIdleTransitionCallback(resourceCallback: IdlingResource.ResourceCallback) {
this.resourceCallback = resourceCallback
}
fun setIdle(isIdleNow: Boolean) {
if (isIdleNow == isIdle.get()) return
isIdle.set(isIdleNow)
if (isIdleNow) {
resourceCallback?.onTransitionToIdle()
}
}
}
object EspressoIdlingResource {
private const val RESOURCE = "GLOBAL"
@JvmField
val idlingResource = SimpleIdlingResource(RESOURCE)
fun busy() {
idlingResource.setIdle(true)
}
fun idle() {
if (!idlingResource.isIdleNow) {
idlingResource.setIdle(false)
}
}
}
Idling Resources 需要再測試的 @Before 及 @After 進行 register & unregister :
@RunWith(AndroidJUnit4::class)
@LargeTest
class TasksActivityTest {
private lateinit var repository: ITasksRepository
private val dataBindingIdlingResource = DataBindingIdlingResource()
@get:Rule
val rule = DaggerTestApplicationRule()
@Before
fun setupDaggerComponent() {
repository = rule.component.tasksRepository
repository.deleteAllTasksBlocking()
}
@Before
fun registerIdlingResource() {
IdlingRegistry.getInstance()
.register(EspressoIdlingResource.idlingResource)
}
@After
fun unregisterIdlingResource() {
IdlingRegistry.getInstance()
.unregister(EspressoIdlingResource.idlingResource)
}
}
在實際的程式碼裡加入 Idling Resource 告知當前忙碌與閒置:
fun View.showSnackbar(snackbarText: String, timeLength: Int) {
Snackbar.make(this, snackbarText, timeLength).run {
addCallback(object: Snackbar.Callback() {
override fun onShown(sb: Snackbar?) {
EspressoIdlingResource.busy()
}
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
EspressoIdlingResource.idle()
}
})
show()
}
}
然後就可以寫一個測試檢查看看 Snackbar
了,有人有興趣的話可以看看拿掉 Idling Resource 後測試的結果如何。
這裏我拿 Snackbar
當例子,絕對不是因為 codelab 也有寫,是因為如果要測 Snackbar 上的文字及顯示與否其實不太好測試。
首先, Snackbar 本身有顯示/隱藏的動畫,然後如果顯示時間設定為 LENGTH_SHORT
,則 Snackbar 會這樣執行:
顯示 -> 出現 2 秒 -> 隱藏
這裡有個問題, Espresso 預設最多 delay 15 毫秒,如果沒有 Idling Resource 告知 Espresso 當前 App 忙碌中,則 Espresso 會馬上跳過 Snackbar 進入下一步測試,理所當然所期望的下個階段的 UI 還沒出現,程式就報錯了。
那現在有了 Idling Resource , Espresso 會在 Snackbar 出現時等待,直到他消失後恢復狀態才進入下個階段的測試。
雖然今天還沒辦法把 Activity 完全做完,不過因為 UI 已經拉好了,我可以完成 Navigatioin Drawer 的測試,在最後附上。