Arrow KT 上的依赖注入

Dependency Injection on Arrow KT

Arrow Kt Documentation on Dependency Injection 中,依赖项在 "Edge of the World" 或 Android 中定义,可以是 ActivityFragment。所以给定的例子如下:

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()
  }
}

但是现在我在想例子中的SettingsActivity怎么会是unit tested呢?由于依赖项是在 activity 中创建的,因此无法再更改以进行测试?

当使用其他一些 Dependency Injection 库时,此依赖项定义是在将要使用的 class 之外创建的。例如在 Dagger 中,创建 Module class 来定义对象(依赖项)的创建方式,并使用 @Inject 来 "inject" 定义的依赖项在模块内部。所以现在当对 Activity 进行单元测试时,我只需要定义一个不同的模块或手动将依赖项的值设置为模拟对象。

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

而不是:

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

你会

interface ActivityApiService {
  fun createId(): String
}

现在您有 2 个实现,一个用于产品

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 无关。检查 'Headless development in Fully Reactive Apps' and the related project https://github.com/pakoito/FunctionalAndroidReference.

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