I appreciate help on this ^
# dagger
j
I appreciate help on this ^
s
Usually this is what I do to create an app that can be used for instrumentation tests. In dagger, I create an
ApplicationComponent
that takes a bunch of inputs in its builder (factory)
These inputs define the edge-of-the-world; eg builld-config input (eg baseUrls for Retrofit), implementations of network interfaces, etc).
The actual
open class MyApp: Application()
builds this
DaggerApplicationComponent
and provides it with these inputs for the ‘real’ world, interface-implementations of actual networking, actual bluetooth, actual base-urls for network-requests.
Then in my instrumentation test, I sub-class
class MyTestableApp : MyApp()
and this one builds the same
DaggerApplicationComponent
but with a different set of inputs. Maybe with fake implementations of hardware/network, maybe with the a base-url of “http://localhost:8888” instead of one of an actual server.
I forgot exactly how, but there is a way to have your instrumentation test use
MyTestablApp
instead of
MyApp
The difference between what I see between my approach and what is shown in the stackoverflow link, is that I use the same
DaggerApplicationComponent
but have a factory that takes input and this input is implemented different on the actual app than on the testable app. I don’t use a
(Dagger)TestAppComponent
Copy code
open class MyApp: Application() {

    ...
    override fun onCreate() {
        ...
        DaggerApplicationComponent
            .builder()
            .applicationContext(this)
            .authServiceConfig(createAuthServiceConfigModule())
            .restServiceConfig(createRestServiceConfigModule())
            .build()
            .inject(this)
    }

    protected val createAuthServiceConfigModule() : AuthServiceConfigModule = authServiceConfigModule
    protected val createRestServiceConfigModule() : RestServiceConfigModule = restServiceConfigModule

    ...
}

// AuthServiceConfigModule is a @Module
private val authServiceConfigModule = AuthServiceConfigModule(
    rootUrl = BuildConfig.ROOT_URL,
    clientId = BuildConfig.CLIENT_ID,
    scope = BuildConfig.SCOPE,
    redirectUrl = BuildConfig.REDIRECT_URL
)

// RestServiceConfigModule is a @Module
private val restServiceConfigModule = RestServiceConfigModule(
    baseUrlFiberServices = BuildConfig.BASE_URL,
    baseUrlForAwsBucket = BuildConfig.AWS_URL,
)
and override the methods
createAuthServiceConfigModule
and
createRestServiceConfigModule
in
MyTestableApp
which will provide `@Module`s
AuthServiceConfigModule
and
RestServiceConfigModule
with local-host base-urls and such.
j
The thing is I guess I do have to inject that component it's not automatically injected, right? So perhaps is that the reason is not up to provide to me some object... how do I inject it?
s
Your
RetroFitModule
provides the OkHttpClient dependency and depending what type of dependant receives it (through constructor injection or field injection) you won't need or will need to manually inject that dependant.
j
@streetsofboston but I don't get how can I use for instance the
@Inject lateinit var retrofit: Retrofit
is not injected... I guess is injected, but I can not get it from the test.
s
You Retrofit Dagger-Module should provide the Retrofit instance. Something like this:
Copy code
@Module
class RetrofitModule(private val baseUrl: String) {
    @Singleton
    @Provides
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder(). ... ... .build()
    }

    @Singleton
    @Provides
    fun provideRetrofit(client: OkHttpClient) {
        return Retrofit.Builer().baseUrl(baseUrl) .. ... .build() 
    }
    ... 
}
Then the actual injection depends on what the target is. If it is field injection, you’d have to do
inject(target)
, when you have
@Inject lateinit var….
. For constructor injection
class MyTarget @Inject constructor(private val ….)
, then you don’t need to ‘manually’ inject.
But it is hard to tell, because in your post and your stackoverflow, you don’t show how the dependency is provided for the class with the `@Inject lateinit var retrofit: Retrofit`…
j
Well, the thing about retrofit baseUrl everything works now, the thins is that on my real app to inject my presenter for instance I do
AndroidInjector.inject(this)
and then I can use
@Inject lateinit var presenter : MyContract.Presenter
so I guess I'm missing something like
AndroidInjector.inject(this)
somewhere, but don't know where to inject this TestAppComponent...
s
Ah…. Yes, I don’t use Dagger-Android… I register singleton ViewModel (or Presenter) Factories with the proper @Inject constructors that get their dependencies injected. From those, I create the actual ViewModel (or Presenter) (a
create(….)
method on those factories.
Copy code
class MyPresenter private constructor(private val reftrofit: Retrofit) {
    class Factory @Inject constructor(private val retrofit: Retrofit) {
        fun create(): MyPresenter = MyPresenter(retrofit)
    }
    ...
}
And I provide singleton instance of
MyPresenter.Factory
in a separate Dagger-Module.
j
Hmmm, so somehow I have to do like
AndroidInjector.inject(this)
for the tests?
s
If you use field-injection, yes.
(one of the reasons I don’t use dagger-android; I use plain presenter or viewmodel factories, maybe through using dagger assisted-injection or just manually)
j
I guess it should be a way to do it with dagger-android, right? Instead of field-injection
s
Yeah… getting rid of dagger-android in the middle of your project may not be a good idea 🙂 Not sure how you could make
AndroidInject
work for tests. You test would be your ‘activity’. I wonder if it is possible to extract something common from your activities and have that be extended by your activities and your tests that would make dagger-android/android-inject work…
j
I've tried about it, but don't know if it's worth that for every test I have to create an injector...
s
Yup, that seems a lot of work. If you are able to use ‘plain’ Presenter-factories instead of android-inject/dagger-android, i’d suggest that. But that may not be feasible in your project/situation…
j
Let's see if someone faced the same problem and solved it with dagger-android... 🙂 Thanks for the help though
s
You’re welcome. I hope you can figure it out. I left my answer on your stackoverflow post as well 🙂