`TestInstallIn` doesn't replace the dependencies when being used to replace dependencies in a `SingletonComponent` for robolectric tests running on a class in a dynamic feature module
See original GitHub issueSo, we have a complicated dagger2 app structure along with multiple dynamic feature modules. I have created a sample project : https://github.com/techeretic/HiltRobolectricIssue that explains this issue. Before I dive into the details, let me give some background about the app
- We have a dagger2 graph being created in the
Application.attachBaseContext
method.
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
earlyInitComponent = DaggerEarlyInitComponent.factory().create()
- The dependencies from dagger graph created in step 1 are bound to the hilt SingletonComponent
@[Module InstallIn(SingletonComponent::class)]
object FirstModule {
@Provides
fun provideEarlyInitDependency(): EarlyInitDependency {
return MainApplication.getInstance().earlyInitComponent.getEarlyInitDependency()
}
- We define a
DynamicFeatureDependencies
EntryPoint which we’ll used to share dependencies with the dagger component in the dynamic feature module
@[EntryPoint InstallIn(SingletonComponent::class)]
interface DynamicFeatureDependencies {
fun getSingletonInterface(): SingletonInterface
}
...
...
@[DFM Component(
dependencies = [DynamicFeatureDependencies::class],
modules = [DynamicFeatureModule::class]
)]
interface DynamicFeatureComponent {
Now, since robolectric tests aren’t supported in a dyamic feature modules, we create a separate Android Library just for running the robolectric tests. This library depends on the app module and the dynamic feature module.
In robolectrictests module, we have configured a robolectric test runner to run with the a custom app (because we have some custom setups being done in our implementation of the Application class).
open class TestRobolectricTestRunner(cls: Class<*>) : RobolectricTestRunner(cls),
GlobalConfigProvider {
// We need it this way in our project
override fun buildGlobalConfig(): Config {
return Config.Builder()
// This is a little hokey but it allows us to set our Robolectric SDK version in a single place.
.setSdk(Config.Builder().setSdk(28).build().sdk[0])
.setApplication(TestApplication::class.java)
.build()
}
override fun get(): Config = Config.Builder().setSdk(28).build()
}
and TestApplication is
class TestApplication : MainApplication() {
lateinit var dynamicFeatureDependencies: DynamicFeatureDependencies
override fun onCreate() {
super.onCreate()
dynamicFeatureDependencies = EntryPoints.get(this, DynamicFeatureDependencies::class.java)
}
We have a SingletonModule
that provides an implementation of SingletonInterface
const val DEFAULT_VALUE = 10
class SingletonDependency : SingletonInterface {
override val someValue: Int
get() = DEFAULT_VALUE
For testing, we setup a test implementation of the SingletonInterface
for our robolectric test
@[Module TestInstallIn(
components = [SingletonComponent::class],
replaces = [SingletonModule::class]
)]
object TestSingletonModule {
@[Provides Singleton]
fun provideSingletonDependency(): SingletonInterface {
return object : SingletonInterface {
override val someValue: Int
get() = TEST_VALUE
override fun doSomething(context: Context) {
// No Op
}
}
}
}
const val TEST_VALUE = 100
Eventually, when we run the test as
@RunWith(TestRobolectricTestRunner::class)
class ExampleUnitTest {
@Inject
lateinit var singletonInterface: SingletonInterface
@Before
fun setUp() {
DaggerRobolectricDynamicFeatureComponent.factory()
.create(
EntryPoints.get(TestApplication.getInstance(), DynamicFeatureDependencies::class.java)
)
.inject(this)
}
@Test
fun addition_isCorrect() {
assertEquals(singletonInterface.someValue, TEST_VALUE)
}
}
It fails.
expected:<10> but was:<100>
Expected :10
Actual :100
<Click to see difference>
Thus, the TEST_VALUE
which is being set in TestSingletonModule
is never part of the hilt singleton dagger graph.
So, either this TestInstallIn
doesn’t work when using custom application implementation (not the @CustomTestApplication) and EntryPoints
used for sharing dependencies with a Dagger2 graph in a dynamic feature module.
(Note: Code snippets here are from a project https://github.com/techeretic/HiltRobolectricIssue that mimics our app code.)
Issue Analytics
- State:
- Created 2 years ago
- Comments:5 (2 by maintainers)
That fixed it!
Thanks!
I’ve updated the Example project with the changes that fixed it. See commit here https://github.com/techeretic/HiltRobolectricIssue/commit/22ff8e9534fe21f05126bd808779a18bf6af5a15
One thing to call out is that I had to move out all the field injection from the
MainApplication
to the class that has the@HiltAndroidApp
. Though this can worked around.I’ll continue with our app migration to hilt from dagger 2
Ah! Lemme try it out.
I was attempting
and ran into this
Thanks for explaining that I have to separate it out.