Hilt: test Android components in isolation
See original GitHub issueUse case
I have this Gradle modules configuration:
app
|
A
/ \
B C
/ \ / \
D E F G
Each one of these Gradle modules is an implementation project('...')
dependency.
Each one of these Gradle modules has a Hilt module:
@Module
@InstallIn(ApplicationComponent::class)
internal object Module[X] { ... }
// for X in (A, B, C, D, E)
Some of these Dagger modules depend on values provided by some other Gradle modules included by app
, or by app
itself.
I have a MainActivity
in the Gradle module A
with some dependencies.
@AndroidEntryPoint
class MainActivity: AppCompatActivity() {
@Inject lateinit var dep1: Dep1
@Inject lateinit var dep2: Dep2
}
I want to test my MainActivity
in isolation providing Dep1
and Dep2
manually in the test.
Something like this:
@HiltAndroidTest
@UninstallModules(...)
class MainActivityTest {
@get:Rule val hiltRule = HiltAndroidRule(this)
@BindValue @JvmStatic val dep1 = mock<Dep1>()
@BindValue @JvmStatic val dep2 = mock<Dep2>()
@Before fun setup() { hiltRule.inject() }
}
As far as I understand, the only way I can do this is by uninstalling all dagger modules of the generated graph of Gradle module A
.
Without uninstalling all modules, dagger will not be able to resolve the dependencies provided by app
unless I provide them in my test with @BindValue
(which would be an overkill).
I should do something like this:
@UninstallModules(
ModuleA::class,
ModuleB::class,
ModuleC::class,
ModuleD::class,
ModuleE::class,
ModuleF::class,
ModuleG::class
)
Is that right?
We have a pretty large project and this is a very common use case.
Sometimes we don’t have access to the dagger modules we want to uninstall, they are in the classpath but they are not visible from the source code for several reasons:
- Dagger modules can be
internal
- Some modules are an
implementation
dependency of anotherimplementation
dependency (see D, E, F and G)
We could make all modules public
and all Gradle dependencies api
but that wouldn’t be a clean solution.
For large projects manually checking all indirect dagger modules to uninstall them would be really painful.
Is there a non-painful way to test Android components in isolation for this use case?
Does it make sense a @UninstallAllModules
annotation that automatically clears the entire dagger graph?
This looked the only way to solve this issue to me, but since I still have a little experience with Hilt, it could sound totally stupid.
This is the way we will probably proceed in our private project to solve this issue, but an “official” API or guidance would be highly appreciated 😄
We solved this issue with Dagger-Android in a really easy way. This is the only reason that stops us from migrating activities/fragment injection to Hilt.
Issue Analytics
- State:
- Created 3 years ago
- Reactions:3
- Comments:19 (10 by maintainers)
Top GitHub Comments
I think it’d be very difficult to set the expectations correctly on what should or shouldn’t work in this case and it isn’t even clear to me where that line should be drawn, especially with extensions like ViewModels or any other extension library someone might create. I also want
@BindValue
to be basically a syntactic sugar as opposed to anything different about using a module to keep things simpler for people so I wouldn’t want to special case it.I think I understand more what you are trying to do and I think I agree this is where it starts to involve a testing philosophy clash. Thanks for realizing that sooner than I did and still continuing to explain it to me =)
Just to clarify, I was referring to new things like
@BindValue
where people aren’t already using it with normal Dagger. In this case, if you did@BindValue Foo foo;
and you used this option to flip the default, you’d also somehow need to reference the generated module somewhere like@InstallModulesIntoThisTest(BindValue_GeneratedFooModule.class)
(class name is an example, I didn’t actually look up what the generated name is). This would probably severely impact the usefulness of such a feature. Same thing would be for other libraries, like you’d probably need to pull in some internal ViewModel Hilt extension modules. So it’d be breaking a lot of encapsulation that Hilt is trying to make easier.Thanks for understanding my point of view and for this discussion. Even though I closed this and your other pull request, I do really appreciate the contributions and effort.