0

文档在测试 Fragments 方面非常好,但没有关于如何测试使用 ActivityResult 的 Activity 的信息。

activityResultRegistry我们应该如何在 Activity 测试中覆盖?

4

2 回答 2

0

将你的合约写在一个单独的文件中,这样你就可以轻松地测试合约并在运行时提供你自己的 ActivityResultRegistry 来伪造预期的结果。实际上调用真正的合约来测试活动是不好的做法。设计合约的核心目的之一是将活动代码与 onActicityResults 分离

class ImageContract(registry: ActivityResultRegistry) {

    private val contractUriResult : MutableLiveData<Uri>  = MutableLiveData(null)

    private val getPermission = registry.register(REGISTRY_KEY, ActivityResultContracts.GetContent()) { uri ->
        contractUriResult.value = uri
    }

    fun getImageFromGallery(): LiveData<Uri> {
        getPermission.launch("image/*")
        return contractUriResult
    }

    companion object {
        private const val REGISTRY_KEY = "Image Picker"
    }
}

在你的活动中

 ImageContractHandler(activityResultRegistry).getImageFromGallery().observe(this, {
      it?.let { u ->
           backgroundImageView.setImageURI(u)
      }
 })

在您的测试中

@Test
fun activityResultTest() {

    // Create an expected result URI
    val testUrl = "file//dummy_file.test"
    val expectedResult = Uri.parse(testUrl)

    // Create the test ActivityResultRegistry
    val testRegistry = object : ActivityResultRegistry() {
        override fun <I, O> onLaunch(
            requestCode: Int,
            contract: ActivityResultContract<I, O>,
            input: I,
            options: ActivityOptionsCompat?
        ) {
            dispatchResult(requestCode, expectedResult)
        }
    }

    val uri = ImageContractHandler(testRegistry).getImageFromGallery().getOrAwaitValue()
    assert(uri == expectedResult)
}

为了在 Tests 中的同一线程上侦听 LiveData,有一个著名的 livedata 测试扩展

fun <T> LiveData<T>.getOrAwaitValue(
    time: Long = 2,
    timeUnit: TimeUnit = TimeUnit.SECONDS,
    afterObserve: () -> Unit = {}
): T {
    var data: T? = null
    val latch = CountDownLatch(1)
    val observer = object : Observer<T> {
        override fun onChanged(o: T?) {
            data = o
            latch.countDown()
            this@getOrAwaitValue.removeObserver(this)
        }
    }
    this.observeForever(observer)

    afterObserve.invoke()

    // Don't wait indefinitely if the LiveData is not set.
    if (!latch.await(time, timeUnit)) {
        this.removeObserver(observer)
        throw TimeoutException("LiveData value was never set.")
    }

    @Suppress("UNCHECKED_CAST")
    return data as T
}
于 2021-09-23T10:27:23.633 回答
0

我没有像我应该的那样准确地阅读文档。

注意:任何允许您ActivityResultRegistry在测试中注入单独的机制都足以启用测试您的活动结果调用。

强调注入这个词。

我在我的项目中使用 Koin,所以我决定使用 Scopes api 创建一个 Activity Scoped 实例ActivityResultRegistry,我将它注入到我的registerForActivityResult-call 中。

val activityScopeModule = module {
    scope<MyActivity> {
        scoped { get<ComponentActivity>().activityResultRegistry }
    }
}
class MyActivity: AppCompatActivity() {
  private val requestPermLauncher = registerForActivityResult(
    ActivityResultContracts.RequestPermission(), 
    get<ActivityResultRegistry>()  // Koin injection
  ) { granted ->
   // handle 
  }
}

通过使用 DI,在测试中注入我的自定义测试实例ActivityResultRegistry变得非常容易。

关于该主题的有用博客文章(使用 Hilt 来完成相同的任务):https ://blog.stylingandroid.com/activity-result-contract-outside-the-activity/

关于 Koin Scopes API 的博客文章:https ://proandroiddev.com/understanding-android-scopes-with-koin-cfe6b60ca579

于 2021-03-02T07:35:43.207 回答