为工具测试注入espresso规则的依赖项

时间:2019-06-21 13:28:33

标签: android android-espresso dagger-2

android studio 3.4.1
dagger-android 2.21

我正在使用Dagger-android将OKHttpClient注入浓缩咖啡规则中。但是还没有找到一种方法,我尝试了很多不同的事情。

这是我正在使用的规则,我正在尝试将okHttpClient注入其中

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)
            }
        }
    }
}

这是我的AndroidTestGoWeatherApplication

class AndroidTestGoWeatherApplication : GoWeatherApplication(), HasActivityInjector {
    @Inject
    lateinit var activityInjector: DispatchingAndroidInjector<Activity>

    override fun activityInjector(): AndroidInjector<Activity> = activityInjector
}

我的申请

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
    }
}

我的主要应用程序组件

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)
}

我的测试应用程序组件

@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
}

这是我创建我的OkHttpClient的TestNetworkModule

@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()
    }
}

我的ActivityBuilder

@Module
abstract class ActivityBuilder {
    @ContributesAndroidInjector(modules = [ActivityModule::class])
    abstract fun injectIntoHomeActivity(): ForecastActivity

    @ContributesAndroidInjector(modules = [ActivityModule::class, ForecastModule::class])
    abstract fun injectIntoForecastFragment(): ForecastFragment
}

我的主要活动

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)
    }
}

我的仪器测试

@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 * 
    }
}

非常感谢

1 个答案:

答案 0 :(得分:2)

我认为您正在以错误的方式将OkHttpClient依赖项注入OkHttpIdingResourceRule的过程。

来自匕首2文档:

  

尽可能使用构造函数注入...

您拥有OkHttpIdingResourceRule,因此您实际上应该在此处进行构造函数注入。

通过将构造函数更改为以下形式,允许Dagger为您构造OkHttpIdingResourceRule

class OkHttpIdingResourceRule @Inject constructor(application: Application, okHttpClient: OkHttpClient)

由于OkHttpClient已经存在于您的对象图中,因此我将OkHttpIdingResourceRule注入到测试{em> 中的OkHttpClient

所有这些,我认为您在代码的其他部分仍然存在一些问题,但是如果没有运行它并且自己看到错误,我无法确认它们。例如,如果您打算以这种方式注入该测试,则必须在测试组件上使用如下方法:

void inject(ForecastActivityAndroidTest test);

编辑:

我再次查看了您的规则,看来您真正感兴趣的是注入IdlingResource。如果是这种情况,则应将构造函数更改为如下形式:

class OkHttpIdingResourceRule @Inject constructor(idlingRes: IdlingResource)

从那里可以做的是在TestNetworkModule中创建一个配置方法,为您创建该方法:

@Provides 
IdlingResource providesIdlingResource(OkHttpClient okHttpClient {
    return OkHttp3IdlingResource.create("okhttp", okHttpClient)
}