In Arrow Kt Documentation on Dependency Injection, the dependency is defined at the "Edge of the World" or in Android could be an Activity
or a Fragment
. So the given example is as follow:
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()
}
}
But now I'm thinking how could the SettingsActivity
in the example could be unit tested
? Since the dependency is created within the activity, it could no longer be changed for testing?
When using some other Dependency Injection
library, this dependency definition is create outside of the class it will be used on. For example in Dagger
, a Module
class is created to define how the objects (dependencies) are created and an @Inject
is used to "inject" the dependency defined inside the module. So now when unit testing the Activity
, I just have to define a different module or manually set the value of the dependency to a mock object.
In Dagger you would create a Mock or Test class that you would @Inject
instead of ActivityApiService
. It is the same here.
Instead of:
class ActivityApiService(val ctx: Context) {
fun createId(): String = doOtherThing(ctx)
}
You do
interface ActivityApiService {
fun createId(): String
}
and now you have 2 implementations, one for prod
class ActivityApiServiceImpl(val ctx: Context): ActivityApiService {
override fun createId(): Unit = doOtherThing(ctx)
}
and another for testing
fun testBla() {
val api = object: ActivityApiService {
override fun createId(): String = "4321"
}
val deps = FetcherDependencies(Either.monadError(), api)
deps.createId("1234") shouldBe "4321"
}
or even use Mockito or a similar tool to create an ActivityApiService
.
I have a couple of articles on how to decouple and unitest outside the Android framework that aren't Arrow-related. Check 'Headless development in Fully Reactive Apps' and the related project https://github.com/pakoito/FunctionalAndroidReference.
If your dependency graph becomes too entangled and you'd like some compile-time magic to create those dependencies, you can always create a local class in tests and @Inject
the constructor there. The point is to decouple from things that aren't unitestable, like the whole Android framework :D