Android 基于Arrow-KT的依赖注入

Android 基于Arrow-KT的依赖注入,android,dependency-injection,arrow-kt,Android,Dependency Injection,Arrow Kt,在中,依赖关系定义在“世界的边缘”,或者在Android中可以是活动或片段。因此,给出的示例如下所示: import Api.* class SettingsActivity: Activity { val deps = FetcherDependencies(Either.monadError(), ActivityApiService(this)) override fun onResume() { val id = deps.createId("1234")

在中,依赖关系定义在“世界的边缘”,或者在Android中可以是
活动
片段
。因此,给出的示例如下所示:

import Api.*

class SettingsActivity: Activity {
  val deps = FetcherDependencies(Either.monadError(), ActivityApiService(this))

  override fun onResume() {
    val id = deps.createId("1234")

    user.text =
      id.fix().map { it.toString() }.getOrElse { "" }

    friends.text =
      deps.getUserFriends(id).fix().getOrElse { emptyList() }.joinToString()
  }
}
但是现在我在想,示例中的
设置活动如何可以
进行单元测试
?由于依赖关系是在活动中创建的,因此无法再为测试更改它


当使用其他一些
依赖项注入
库时,此依赖项定义是在将在其上使用的类之外创建的。例如,在
Dagger
中,创建一个
模块
类来定义如何创建对象(依赖项),并使用
@Inject
来“注入”模块内定义的依赖项。因此,现在当对
活动进行单元测试时,我只需要定义一个不同的模块或手动将依赖项的值设置为一个模拟对象。

在Dagger中,您将创建一个模拟或测试类,您将
@Inject
而不是
ActivityApiService
。这里也一样

而不是:

class ActivityApiService(val ctx: Context) {
  fun createId(): String = doOtherThing(ctx)
}
是吗

interface ActivityApiService {
  fun createId(): String
}
现在您有了两个实现,一个用于prod

class ActivityApiServiceImpl(val ctx: Context): ActivityApiService {
  override fun createId(): Unit = doOtherThing(ctx)
}
另一个用于测试

fun testBla() {
  val api =  object: ActivityApiService {
    override fun createId(): String = "4321"
  }

  val deps = FetcherDependencies(Either.monadError(), api)

  deps.createId("1234") shouldBe "4321"
}
甚至可以使用Mockito或类似的工具来创建
ActivityApiService

我有几篇关于如何在Android框架之外进行解耦和统一测试的文章,这些文章与Arrow无关。检查并完成相关项目


如果您的依赖关系图变得过于复杂,并且您需要一些编译时魔法来创建这些依赖关系,那么您可以始终在测试中创建一个本地类,并在那里插入构造函数。关键是要与不可统一的东西分离,比如整个Android框架:D

在Dagger中,您将创建一个模拟或测试类,您将
@Inject
而不是
ActivityApiService
。这里也一样

而不是:

class ActivityApiService(val ctx: Context) {
  fun createId(): String = doOtherThing(ctx)
}
是吗

interface ActivityApiService {
  fun createId(): String
}
现在您有了两个实现,一个用于prod

class ActivityApiServiceImpl(val ctx: Context): ActivityApiService {
  override fun createId(): Unit = doOtherThing(ctx)
}
另一个用于测试

fun testBla() {
  val api =  object: ActivityApiService {
    override fun createId(): String = "4321"
  }

  val deps = FetcherDependencies(Either.monadError(), api)

  deps.createId("1234") shouldBe "4321"
}
甚至可以使用Mockito或类似的工具来创建
ActivityApiService

我有几篇关于如何在Android框架之外进行解耦和统一测试的文章,这些文章与Arrow无关。检查并完成相关项目


如果您的依赖关系图变得过于复杂,并且您需要一些编译时魔法来创建这些依赖关系,那么您可以始终在测试中创建一个本地类,并在那里插入构造函数。关键是要与不可统一的东西分离,比如整个Android框架:D

这是测试依赖关系,但不是测试活动本身。是的,虽然技术上的测试活动不是单元测试(因为它与android本身有依赖关系),但我们可能想单独测试活动,例如当单击按钮时,它是否调用正确的函数或参数,这是非常可行的。我不确定我是否理解正确。我的建议是与Android框架分离,所以你测试的是表示层。当您执行一个操作时,您不会直接触发UI,而是创建一个新状态。然后,表示层从该状态转换为单击UI。另一个方向也一样。这样,当新的输入发生时,测试就变成了状态之间的测试转换。请参阅“Headless development”中的示例,该示例介绍了我如何仅使用可观察项对登录流进行端到端测试。如果您想测试您的表示层是否单击了UI上的真实按钮,或者您是否完全跳过了表示层,那么您就处于E2E领域,这是一组完全不同的测试运行程序和断言。让它们稳定下来是一件痛苦的事情,这只是测试依赖关系,而不是测试活动本身。是的,虽然技术上的测试活动不是单元测试(因为它与android本身有依赖关系),但我们可能想单独测试活动,例如当单击按钮时,它是否调用正确的函数或参数,这是非常可行的。我不确定我是否理解正确。我的建议是与Android框架分离,所以你测试的是表示层。当您执行一个操作时,您不会直接触发UI,而是创建一个新状态。然后,表示层从该状态转换为单击UI。另一个方向也一样。这样,当新的输入发生时,测试就变成了状态之间的测试转换。请参阅“Headless development”中的示例,该示例介绍了我如何仅使用可观察项对登录流进行端到端测试。如果您想测试您的表示层是否单击了UI上的真实按钮,或者您是否完全跳过了表示层,那么您就处于E2E领域,这是一组完全不同的测试运行程序和断言。让他们稳定下来是一件痛苦的事情。