@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.
I think there is some room for improvement in the documentation, but the suggested approach here is to move your printerFactory
into a base class that both your production app and the custom test app will extend.
class BaseApp : Application() {
@Inject lateinit var printerFactory: PrinterFactory
Your prod app now just extends the new base:
@HiltAndroidApp
class MyApplication: BaseApp()
and your custom test app should too:
@CustomTestApplication(BaseApp::class)
interface HiltTestApplication
you'll also have to updates usages of your app, instead of casting the app context or app to your production app class cast it to the base class.
Unfortunately that results in:
error: [Hilt]
public static abstract interface HiltTestApplication {
@CustomTestApplication does not support application classes (or super classes) with @Inject fields. Found BaseApp with @Inject fields [printerFactory].
Ah, my bad, I forgot about that check. :(
Added on ecd9e8f, you'll find the reason in the commit message. Due to injection timing we opted for banning injected fields in the test app instead of letting users run into NPEs due to injection not occurring in the App's onCreate()
during a test.
There is probably a few paths you can take but if your intent is to scope and make printerFactory
available to those who can get a hold of the app context, then maybe just scoping it (adding @Singleton
to the provider) and creating an entry point for it might be a nice way make it available in a more on-demand fashion.
class BaseApp : Application() {
fun getPrinterFactory() =
EntryPointsAccessors.fromApplication(this, PrinterFactoryEntryPoint::class.java).getPrinterFactory()
@EntryPoint
@InstallIn(ApplicationComponent::class)
interface PrinterFactoryEntryPoint {
fun getPrinterFactory(): PrinterFactory
Bad Solution
Regarding the this documentation I create interface and put my test application:
@CustomTestApplication(TestApplication::class)
interface AndroidTestApplication
But it gives me exception:
Caused by: java.lang.InstantiationException: java.lang.Class<com.myapp.AndroidTestApplication> cannot be instantiated
at java.lang.Class.newInstance(Native Method)
at android.app.Instrumentation.newApplication(Instrumentation.java:1165)
at com.myapp..AndroidTestRunner.newApplication(AndroidTestRunner.kt:16)
at android.app.LoadedApk.makeApplication(LoadedApk.java:1218)
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)
Tougee, 0xGuybrush, sergiomr88, hgross, sivarooban-swirepay, Maragues, dhruv2295, imohsenb, and akexorcist reacted with thumbs up emoji
0xGuybrush and sivarooban-swirepay reacted with hooray emoji
rohitkaradkar reacted with rocket emoji
All reactions
@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
@CustomTestApplication(BaseApplication::class)
interface TestApplication
open class BaseApplication : Application()
@HiltAndroidApp
open class MainApplication : BaseApplication()
Is there any possible way to execute instrumented tests with the following structure using hilt 2.32-alpha?
It looks like there is no straight forward way with the @EntryPoint
/@EntryPointAccessors
pattern inside the BaseApplication
. The structure works fine for the app, but not for instrumented tests with MyCustomTestApplication
and MyCustomTestRunner
.
I am able to run instrumented tests based on MyCustomTestApplication
when I remove the entry point from BaseApplication
.
Any clues how to get injection wiht hilt inside BaseApplication
working?
Example:
// src
@CustomTestApplication(BaseApplication::class)
interface TestApplication
// src
open class BaseApplication : Application() {
@EntryPoint
@InstallIn(SingletonComponent::class)
interface SomeDependencyEntryPoint {
fun getSomeDependency(): ISomeDependency
// no @Inject, since "@CustomTestApplication does not support application classes (or super classes) with @Inject fields."
// we make use of the "EntryPointAccessors-pattern" instead
lateinit var someDependency: ISomeDependency
override fun onCreate() {
super.onCreate()
someDependency = EntryPointAccessors.fromApplication(this, SomeDependencyEntryPoint::class.java).getSomeDependency()
someDependency.doSomething();
// src
@HiltAndroidApp
open class MainApplication : BaseApplication()
// androidTest src
@CustomTestApplication(BaseApplication::class)
interface MyCustomTestApplication
// A custom runner to set up the instrumented application class for tests.
// see https://developer.android.com/training/dependency-injection/hilt-testing
// must be referenced in the build.gradle
class MyCustomTestRunner : AndroidJUnitRunner() {
override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
// compilation errors in the IDE here are expected until first build (according to HILT docs)
return super.newApplication(cl, MyCustomTestApplication_Application::class.java.name, context)
// build.gradle of app module
android {
defaultConfig {
testInstrumentationRunner "eu.hgross.example.MyCustomTestApplication"
Hi @hgross,
We're currently working on a feature to allow entry points in such a case (with some caveats); however, it's still important to understand why this doesn't work by default in Hilt so that you can decide if this is something you really want to do.
First, if you have to reuse the BaseApplication
in Gradle instrumentation tests be careful about mutable state because the same application instance is used for all test classes and test cases in Gradle instrumentation tests making it very easy to leak state across test cases. Instead, try moving all application state into the Hilt SingletonComponent. Each test case (even for Gradle instrumentation tests) will get a new instance of the SingletonComponent
, so @Singleton
scoped bindings will not be leaked across test cases.
In addition, try to avoid entry point calls in the application. While calling entry points lazily, as in #2033 (comment) sometimes works, it would better to provide it via a module instead, if possible.
That said, we do understand that there are some cases that required calling entry points in Application#onCreate()
, e.g. some libraries require some static configuration. The main reason this doesn't work by default in Hilt is that at the time of Application#onCreate()
there is no SingletonComponent
available (remember that in Hilt tests, the SingletonComponent instance is created per test case rather than per Application). For these cases, we are working on an escape hatch to make it possible to use entry points from Applicaiton#onCreate()
for special entry points.
For these cases, we are working on an escape hatch to make it possible to use entry points from Applicaiton#onCreate() for special entry points.
@bcorso are you referring to EarlyEntryPoints?
Instead, try moving all application state into the Hilt SingletonComponent.
What about the scenario where you need to @Inject
some fields into the Application
but those fields are also invoked within Application#onCreate
as part of initialization logic? For example, a Set<LifecycleObserver>
where in onCreate()
, you wish to add them to the ProcessLifecycleOwner
? Another example would be where a custom AppInitializer
interface is defined and the Application
gets injected with a Set<AppInitializer>
or List<AppInitializer>
and all AppInitializers
need to be invoked during onCreate
. It is not clear to me how these would apply to the statement above. 🤔
@mhernand40, for now we still don't allow using @Inject
fields in the application class (it's possible we allow this in the future, but that's questionable).
However, you can create an @EarlyEntryPoint
to replace the @Inject
fields for the application class, like:
class BaseApplication extends Application {
// Use EarlyEntryPoint rather than @Inject for these fields since they need to be accessed in onCreate in tests.
@EarlyEntryPoint
interface ApplicationEarlyEntryPoint {
Foo foo();
Bar bar();
private Foo foo;
private Bar bar;
@Override
public void onCreate() {
super.onCreate();
foo = EarlyEntryPoints.get(this, ApplicationEarlyEntryPoint.class).foo();
bar = EarlyEntryPoints.get(this, ApplicationEarlyEntryPoint.class).bar();
While you could even just create an @EarlyEntryPoint
injector by adding an inject method for the application, like: void inject(MyTestApplication app)
, it would lead to double injection if you use the base application in non-test applications.
Thanks for the detailed answer and valuable testing hints @bcorso . One of the use cases @mhernand40 mentioned does apply to my requirements. I want to register a HILT-injected component to the lifecycle callbacks in the application class. This component is repsonsible to orchestrate initialization and teardown/shutdown.
Regarding the BaseApplication
: This turns out to be a quite time consuming endavour for me as well, since I am dealing with a multi-module project and finding a structure to re-use the custom test-runner for instrumented tests in sub-/parent-modules is not at all straight forward to me. Is there any up-to-date multi-module best-practice project-example for multi-module projects and the whole testing pyramid (unit, instrumented/integration, ui-tests)?
Although @EarlyEntryPoint works for injecting into the Application#onCreate, the same dependency if is injected to another Android component such as a Service seems produce two instances of such dependency (even though annotated with @singleton).
In my case I have something like this:
@Singleton
class Foo { @Inject constructor}
then in my BaseApplication I have this:
private Foo;
@EarlyEntryPoint
@InstallIn(SingletonComponent.class)
interface HiltEntryPoint {
Foo getFoo();
onCreate(){
foo = EarlyEntryPoints.get(this, HiltEntryPoint.class).getFoo();
Then in one of my service I have:
@Inject
Foo foo;
The result is that the foo in my Service is different than the foo in my BaseApplication.
@namgk, that's correct. The @Inject Foo
field does not come from the EarlyEntryPoint component. If you want the early entry point Foo
in your service you would have to also get it with an early entry point EarlyEntryPoints.get(this, HiltEntryPoint.class).getFoo()
.
However, you may be able to avoid this issue if it's possible for you to create Foo
lazily rather than in Application#onCreate
and just use a normal entry point, like:
@EntryPoint
@InstallIn(SingletonComponent.class)
interface HiltEntryPoint {
Foo getFoo();
private Foo foo() {
// Create this lazily rather than in onCreate to avoid needing an @EarlyEntryPoint
return EntryPoints.get(this, HiltEntryPoint.class).getFoo();
Works for me. Interesting things to learn along the way :)
I think the way hilt works with tests isn't always intuitive. Like one
instance of application but different instance of SingletonComponent.
Where or when, or how such SingletonComponents are created by the way, if
not tied to Application#onCreate?
On Thu, May 6, 2021 at 8:37 PM Brad Corso ***@***.***> wrote:
@namgk <
https://github.com/namgk>, that's correct. The
@Inject Foo field
does not come from the EarlyEntryPoint component. If you want the early
entry point Foo in your service you would have to also get it with an
early entry point EarlyEntryPoints.get(this,
HiltEntryPoint.class).getFoo().
However, you may be able to avoid this issue if it's possible for you to
create Foo lazily using a normal entry point, like:
@entrypoint
@Installin(SingletonComponent.class)
interface HiltEntryPoint {
Foo getFoo();
private Foo foo() {
// Create this lazily rather than in onCreate to avoid needing an @EarlyEntryPoint
return EntryPoints.get(this, HiltEntryPoint.class).getFoo();
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<
#2033 (comment)>, or
unsubscribe
<
https://github.com/notifications/unsubscribe-auth/AAHU7IEBZ5332ZHJ7Y5P7QTTMNN7FANCNFSM4PZJJ64Q>
I think the way hilt works with tests isn't always intuitive. Like one
instance of application but different instance of SingletonComponent.
Where or when, or how such SingletonComponents are created by the way, if
not tied to Application#onCreate?
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 the HiltAndroidRule
rather than the Application
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 from Application#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 created EarlyEntryPoint
, but as you've noticed, the binding is created from a completely different component that has the lifetime of the Application
rather than the HiltAndroidRule
. In general, you should avoid using EarlyEntryPoint
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
Thanks Brad for the detailed response, I totally missed
the HiltAndroidRule, makes sense now.
On Fri, May 7, 2021 at 8:15 AM Brad Corso ***@***.***> wrote:
I think the way hilt works with tests isn't always intuitive. Like one
instance of application but different instance of SingletonComponent.
Where or when, or how such SingletonComponents are created by the way, if
not tied to Application#onCreate?
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 the HiltAndroidRule rather than the Application
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 from
Application#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 created EarlyEntryPoint, but as you've
noticed, the binding is created from a completely different component that
has the lifetime of the Application rather than the HiltAndroidRule. In
general, you should avoid using EarlyEntryPoint 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
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<
#2033 (comment)>, or
unsubscribe
<
https://github.com/notifications/unsubscribe-auth/AAHU7IDBAXYWP7LBDBDMU63TMP7ZZANCNFSM4PZJJ64Q>
What I normally do, is to uninstall everything from the device, maybe do a
clean and rebuild/reinstall. I think this has to do with the way Android
does partial apk updates in development.
On Wed, Jul 7, 2021 at 6:59 AM Dawid Hyży ***@***.***> wrote:
I am getting
java.lang.RuntimeException: Unable to create application
com.example.HiltTestApplication_Application:
java.lang.IllegalStateException: The component was not created. Check that
you have added the HiltAndroidRule.
in the BaseApplication. Did you encounter that? My test class has
HiltAndroidRule
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<
#2033 (comment)>, or
unsubscribe
<
https://github.com/notifications/unsubscribe-auth/AAHU7IFSEVJEJB4XXALACZ3TWRMT3ANCNFSM4PZJJ64Q>
I've been trying to follow the steps above, but I'm getting the following error, when trying to access context from the application's onCreate.
MainApplication
@HiltAndroidApp
open class MainApplication : BaseApplication() {
HiltTestApplication
@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication
BaseApplication
abstract class BaseApplication: Application()
lateinit var foo: Foo
@EarlyEntryPoint
@InstallIn(SingletonComponent::class)
internal interface ApplicationEarlyEntryPoint {
val foo: Foo
override fun onCreate() {
super.onCreate()
foo = EarlyEntryPoints.get(this <---- ERROR HERE, ApplicationEarlyEntryPoint::class.java).foo
......
Results in:
Expected application context to implement GeneratedComponentManagerHolder. Check that you're passing in an application context that uses Hilt.
Is there a step I have missed somewhere? Thanks in advance!
Add the application class to the error message when it doesn't implement GeneratedComponentManager in EarlyEntryPoints.
#3540
guys I am still getting following error
java.lang.IllegalStateException: Hilt test, com.chargeatfriends.android.ui.settings.login.SignInFragmentTest, cannot use a @HiltAndroidApp application but found com.chargeatfriends.android.Application. To fix, configure the test to use HiltTestApplication or a custom Hilt test application generated with @CustomTestApplication.
at dagger.hilt.internal.Preconditions.checkState(Preconditions.java:83)
at dagger.hilt.android.internal.testing.MarkThatRulesRanRule.<init>(MarkThatRulesRanRule.java:63)
at dagger.hilt.android.testing.HiltAndroidRule.<init>(HiltAndroidRule.java:36)
at com.chargeatfriends.android.ui.settings.login.SignInFragmentTest.<init>(SignInFragmentTest.kt:36)
@kyodgorbek, you're going to have to give more information for us to help you.
Which of the suggestions did you try (there's two)?
What's the error message when you tried the suggested approach? Is it the same or a different stacktrace?
Can you provide code snippets or example project that reproduce the issue?
Also, since this issue is already closed, it's probably better to start a new issue with all of these details.
Exception when using Hilt injection in test class when using androidx.startup Initializer injection
#3906