dagger: @CustomTestApplication value cannot be annotated with @HiltAndroidApp
I have an application that needs injection:
@HiltAndroidApp
class MyApplication: Application() {
@Inject lateinit var printerFactory: PrinterFactory
...
}
Other code in the app uses application.printerFactory
.
The printerFactory
is provided by this:
@Module
@InstallIn(ApplicationComponent::class)
object ProdModule {
@Provides
fun providePrinterFactory(): PrinterFactory {
return FactoryThatReturnsARealPrinter()
}
}
Now, for instrumented tests, I need to override this with a test version:
@HiltAndroidTest
@UninstallModules(ProdModule::class)
@RunWith(AndroidJUnit4::class)
@LargeTest
class MainTest {
@get:Rule(order = 0)
var hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
var intentsTestRule = IntentsTestRule(MainActivity::class.java, false, false)
// Test replacements for production stuff.
@Module
@InstallIn(ApplicationComponent::class)
class TestModule {
@Provides
fun providePrinterFactory(): PrinterFactory {
return FactoryThatReturnsATestPrinter()
}
}
...
}
And so I get this error:
java.lang.IllegalStateException: Hilt test, MainTest, cannot use a @HiltAndroidApp application but found MyApplication. To fix, configure the test to use HiltTestApplication or a custom Hilt test application generated with @CustomTestApplication.
Well, I can’t use HiltTestApplication because I need to use MyApplication, which has dependencies injected.
So I added this:
@CustomTestApplication(MyApplication::class)
interface HiltTestApplication
And so I get this error:
error: [Hilt]
public static abstract interface HiltTestApplication {
^
@CustomTestApplication value cannot be annotated with @HiltAndroidApp. Found: MyApplication
If MyApplication
cannot be annotated with @HiltAndroidApp
, then how is it expected to inject things?
Instructions unclear, ended up in inconsistent state.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 7
- Comments: 25 (3 by maintainers)
Commits related to this issue
- Add the application class to the error message when it doesn't implement GeneratedComponentManager in EarlyEntryPoints. Issue #2033. RELNOTES=n/a PiperOrigin-RevId: 468766554 — committed to google/dagger by Chang-Eric 2 years ago
- Add the application class to the error message when it doesn't implement GeneratedComponentManager in EarlyEntryPoints. Issue #2033. RELNOTES=n/a PiperOrigin-RevId: 468809118 — committed to google/dagger by Chang-Eric 2 years ago
Bad Solution Regarding the this documentation I create interface and put my test application:
But it gives me exception:
So i’m directly passing the generated hilt application class for my test app and skip using
AndroidTestApplication
interface and it works:Instrumentation.newApplication(AndroidTestApplication_Application::class.java, context)
@danysantiago I got the following issue when I use a base app. Do I missing something else ?
java.lang.RuntimeException: Unable to instantiate application com.sample.MainApplication: java.lang.InstantiationException: java.lang.Class<com.sample.di.TestApplication> cannot be instantiated
@bcorso are you referring to EarlyEntryPoints?
What about the scenario where you need to
@Inject
some fields into theApplication
but those fields are also invoked withinApplication#onCreate
as part of initialization logic? For example, aSet<LifecycleObserver>
where inonCreate()
, you wish to add them to theProcessLifecycleOwner
? Another example would be where a customAppInitializer
interface is defined and theApplication
gets injected with aSet<AppInitializer>
orList<AppInitializer>
and allAppInitializers
need to be invoked duringonCreate
. It is not clear to me how these would apply to the statement above. 🤔A lot of the complexity around testing is due to how Gradle runs instrumentation tests. Gradle will run all tests using a single instance of an Application. This means
Application#onCreate()
gets called only once no matter how many tests and test cases you’re running. It also means that storing any state in the application class will leak that state across all of your test cases. To avoid this issue, Hilt creates and stores the SingletonComponent using theHiltAndroidRule
rather than theApplication
so that each test case gets its own component instance and is independent from other test cases.However, as you’ve likely seen, using
HiltAndroidRule
to create the SingletonComponent causes issues if you try to call entry points fromApplication#onCreate()
because the SingletonComponent has not yet been created (and even if you could create one, it’s not clear which one you would use since each test case has its own?).For cases where you absolutely need to access an entry point in
Application#onCreate()
we’ve createdEarlyEntryPoint
, but as you’ve noticed, the binding is created from a completely different component that has the lifetime of theApplication
rather than theHiltAndroidRule
. In general, you should avoid usingEarlyEntryPoint
unless you have no other choice because it can lead to the issues you described of two instances of a singleton component.For more details see https://dagger.dev/hilt/early-entry-point#background