Reputation: 22566
android studio 3.4.1
dagger-android 2.21
I am using dagger-android to inject my OKHttpClient into a espresso rule. But haven't found a way to do this, I have attempted many differnt things.
This is my rule I am using and I am trying to inject the okHttpClient into it
class OkHttpIdingResourceRule(application: Application) : TestRule {
/* My attempt below - but not working */
private val testApplication =
InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
as AndroidTestGoWeatherApplication
// private val testApplication = application.applicationContext as AndroidTestGoWeatherApplication
private val component = testApplication.component as AndroidTestGoWeatherPresentationComponent
private val okHttpClient: OkHttpClient = component.okHttpClient()
private val idlingResource: IdlingResource = OkHttp3IdlingResource.create("okhttp", okHttpClient)
override fun apply(base: Statement?, description: Description?): Statement {
return object: Statement() {
override fun evaluate() {
IdlingRegistry.getInstance().register(idlingResource)
base?.evaluate()
IdlingRegistry.getInstance().unregister(idlingResource)
}
}
}
}
This is my AndroidTestGoWeatherApplication
class AndroidTestGoWeatherApplication : GoWeatherApplication(), HasActivityInjector {
@Inject
lateinit var activityInjector: DispatchingAndroidInjector<Activity>
override fun activityInjector(): AndroidInjector<Activity> = activityInjector
}
My Application
open class GoWeatherApplication : Application(), HasActivityInjector, HasSupportFragmentInjector, HasServiceInjector {
@Inject
lateinit var dispatchingAndroidActivityInjector: DispatchingAndroidInjector<Activity>
@Inject
lateinit var dispatchingAndroidFragmentInjector: DispatchingAndroidInjector<Fragment>
@Inject
lateinit var dispatchingAndroidServiceInjector: DispatchingAndroidInjector<Service>
lateinit var component: GoWeatherComponent
override fun onCreate() {
super.onCreate()
component = DaggerGoWeatherComponent
.builder()
.application(this)
.build()
component.inject(this)
}
override fun activityInjector(): AndroidInjector<Activity> {
return dispatchingAndroidActivityInjector
}
override fun supportFragmentInjector(): AndroidInjector<Fragment> {
return dispatchingAndroidFragmentInjector
}
override fun serviceInjector(): AndroidInjector<Service> {
return dispatchingAndroidServiceInjector
}
}
My main application Component
GoWeatherComponent
@Singleton
@Component(modules = [
AndroidSupportInjectionModule::class,
ActivityBuilder::class,
NetworkModule::class,
GoWeatherApplicationModule::class])
interface GoWeatherComponent {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: GoWeatherApplication): Builder
fun build(): GoWeatherComponent
}
fun inject(application: GoWeatherApplication)
}
My test application component
@Singleton
@Component(modules = [
AndroidSupportInjectionModule::class,
TestNetworkModule::class,
TestGoWeatherApplicationModule::class,
TestForecastModule::class])
interface AndroidTestGoWeatherPresentationComponent : AndroidInjector<AndroidTestGoWeatherApplication> {
@Component.Builder
abstract class Builder : AndroidInjector.Builder<AndroidTestGoWeatherApplication>() {
abstract fun applicationModule(TestApplicationModule: TestGoWeatherApplicationModule): Builder
abstract fun testNetworkModule(testNetworkModule: TestNetworkModule): Builder
}
fun okHttpClient(): OkHttpClient
}
This is my TestNetworkModule where I am creating my OkHttpClient
@Module
class TestNetworkModule {
@Singleton
@Provides
fun httpLoggingInterceptor(): HttpLoggingInterceptor {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = if(BuildConfig.DEBUG) {
HttpLoggingInterceptor.Level.BODY
}
else {
HttpLoggingInterceptor.Level.NONE
}
return loggingInterceptor
}
@Singleton
@Provides
fun provideOkHttpClient(httpLoggingInterceptor: HttpLoggingInterceptor): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
.connectTimeout(2, TimeUnit.SECONDS)
.readTimeout(2, TimeUnit.SECONDS)
.build()
}
@Named("TestBaseUrl")
@Singleton
@Provides
fun provideBaseUrlTest(): String =
"http://localhost:8080/"
@Singleton
@Provides
fun provideRetrofit(@Named("TestBaseUrl") baseUrl: String, okHttpClient: OkHttpClient?): Retrofit {
return Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient!!)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
}
My ActivityBuilder
@Module
abstract class ActivityBuilder {
@ContributesAndroidInjector(modules = [ActivityModule::class])
abstract fun injectIntoHomeActivity(): ForecastActivity
@ContributesAndroidInjector(modules = [ActivityModule::class, ForecastModule::class])
abstract fun injectIntoForecastFragment(): ForecastFragment
}
My main Activity
class ForecastActivity : AppCompatActivity(), ForecastView, RetryListener, LocationUtilsListener {
companion object {
const val WEATHER_FORECAST_KEY = "weatherForecast"
}
@Inject
lateinit var forecastPresenter: ForecastPresenter
@Inject
lateinit var location: LocationUtils
private var fragmentManager: FragmentManager? = null
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
}
}
My instrumention test
@RunWith(AndroidJUnit4::class)
class ForecastActivityAndroidTest {
@Inject
lateinit var okHttpClient: OkHttpClient
@get:Rule
val okHttpIdingResourceRule = OkHttpIdingResourceRule(InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as AndroidTestGoWeatherApplication)
@get:Rule
val activityRule = ActivityTestRule(ForecastActivity::class.java, false, false)
private val mockWebserver: MockWebServer by lazy {
MockWebServer()
}
@Before
fun setUp() {
val testApplication =
InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
as AndroidTestGoWeatherApplication
DaggerAndroidTestGoWeatherPresentationComponent
.builder()
.applicationModule(TestGoWeatherApplicationModule())
.create(testApplication)
.inject(testApplication)
mockWebserver.start(8080)
}
@After
fun tearDown() {
mockWebserver.shutdown()
}
@Test
fun should_load_five_day_forecast() {
loadFromResources("json/fivedayforecast.json")
mockWebserver.enqueue(MockResponse().setBody(loadFromResources("json/fivedayforecast.json")))
ActivityScenario.launch(ForecastActivity::class.java)
/* do some testing here *
}
}
Many thanks in advance
Upvotes: 4
Views: 1304
Reputation: 736
I think you're going about the process of injecting the OkHttpClient
dependency into the OkHttpIdingResourceRule
the wrong way.
From the dagger 2 docs:
Constructor injection is preferred whenever possible...
You own OkHttpIdingResourceRule
so you should really be doing constructor injection here.
Allow Dagger to construct OkHttpIdingResourceRule
for you by changing the constructor to look like this:
class OkHttpIdingResourceRule @Inject constructor(application: Application, okHttpClient: OkHttpClient)
Since OkHttpClient
is already in your object graph I would inject OkHttpIdingResourceRule
into your test instead of OkHttpClient
All this being said, I think you still have some issues with other parts of the code, but without running it and seeing the errors for myself I can't confirm them. For example, if you plan on injecting this test in this manner then you'll have to have a method like this on your test component:
void inject(ForecastActivityAndroidTest test);
Edit:
I've looked at your rule again and it looks like what you're really interested in is injecting the IdlingResource
. If this is the case, you should change the constructor to look like this:
class OkHttpIdingResourceRule @Inject constructor(idlingRes: IdlingResource)
From there what you can do is create a provision method in your TestNetworkModule
that creates it for you:
@Provides
IdlingResource providesIdlingResource(OkHttpClient okHttpClient {
return OkHttp3IdlingResource.create("okhttp", okHttpClient)
}
Upvotes: 2