【问题标题】:(DAGGER-ANDROID) Can not use @Inject on an Espresso Test and can not use mockWebServer(DAGGER-ANDROID) 不能在 Espresso 测试中使用 @Inject 并且不能使用 mockWebServer
【发布时间】:2020-05-01 08:37:42
【问题描述】:

我正在尝试创建 Espresso 测试并使用 mockWebServer 问题是,当我尝试创建 mockWebServer 时,它会调用真正的 api 调用,我想拦截它并模拟响应。

我的匕首组织是:

我的应用

open class App : Application(), HasAndroidInjector {

    lateinit var application: Application

    @Inject
    lateinit var androidInjector: DispatchingAndroidInjector<Any>

    override fun androidInjector(): AndroidInjector<Any> = androidInjector

    override fun onCreate() {
        super.onCreate()
        DaggerAppComponent.factory()
            .create(this)
            .inject(this)
        this.application = this
    }
}

然后是 MyAppComponent

@Singleton
@Component(
    modules = [
        AndroidInjectionModule::class,
        AppModule::class,
        RetrofitModule::class,
        RoomModule::class,
        AppFeaturesModule::class
    ]
)
interface AppComponent : AndroidInjector<App> {

    @Component.Factory
    interface Factory {
        fun create(@BindsInstance application: App): AppComponent
    }
}

然后我创建了这个TestApp

class TestApp : App() {

    override fun androidInjector(): AndroidInjector<Any> = androidInjector

    override fun onCreate() {
        DaggerTestAppComponent.factory()
            .create(this)
            .inject(this)
    }
}

这是我的 TestAppComponent

@Singleton
@Component(
    modules = [
        AndroidInjectionModule::class,
        AppModule::class,
        TestRetrofitModule::class,
        AppFeaturesModule::class,
        RoomModule::class]
)
interface TestAppComponent : AppComponent {
    @Component.Factory
    interface Factory {
        fun create(@BindsInstance application: App): TestAppComponent
    }
}

注意:这里我创建了一个新模块,名为 TestRetrofitModule,其中 BASE_URL 为“http://localhost:8080”,我不知道是否需要其他内容。

我还创建了TestRunner

class TestRunner : AndroidJUnitRunner() {

    override fun newApplication(
        cl: ClassLoader?,
        className: String?,
        context: Context?
    ): Application {
        return super.newApplication(cl, TestApp::class.java.name, context)
    }

}

并将其放在testInstrumentationRunner

问题 1

我不能用

@Inject
lateinit var okHttpClient: OkHttpClient

因为它说它没有初始化。

问题 2(已解决,感谢 Skizo)

我的 mockWebServer 没有发送响应,即使它没有指向真正的 api 调用,而是指向我已经放入 TestRetrofitModule 的那个,问题是我必须链接那个 mockWebServer 和 Retrofit。

【问题讨论】:

  • 请使用扩展您的 AbstractBaseTest 的类的构造函数代码编辑您的问题

标签: android kotlin android-espresso dagger-2 dagger


【解决方案1】:

您发布的设置看起来正确。至于没有提供App,您可能需要在组件中绑定它,因为现在您只绑定TestApp。所以你需要更换

fun create(@BindsInstance application: TestApp): TestAppComponent

fun create(@BindsInstance application: App): TestAppComponent

【讨论】:

  • 是的,没错,但问题是 Dispatcher 不工作,如何将 mockWebServer 链接到假调用?
【解决方案2】:

我最近与mockWebServer 有同样的问题,你需要做的是放置一个断点,看看是什么错误,在我的情况下,我把它放在我正在打电话的BaseRepository 上,然后发现例外是:

java.net.UnknownServiceException: CLEARTEXT communication to localhost not permitted by network security policy

我为解决这个问题所做的就是将这个添加到我的manifest.xml

android:usesCleartextTraffic="true"

但您可能必须使用其他方法,您可以查看android-8-cleartext-http-traffic-not-permitted

【讨论】:

    【解决方案3】:

    当我尝试做类似的事情时,我不会创建两种类型的应用程序组件,而只是一种。我根据实际的App 还是TestApp 为他们提供不同的输入。根本不需要TestAppComponent。例如

    open class App : Application(), HasAndroidInjector {
    
        lateinit var application: Application
    
        @Inject
        lateinit var androidInjector: DispatchingAndroidInjector<Any>
    
        override fun androidInjector(): AndroidInjector<Any> = androidInjector
    
        override fun onCreate() {
            super.onCreate()
            DaggerAppComponent.factory()
                .create(this, createRetrofitModule())
                .inject(this)
            this.application = this
        }
    
        protected fun createRetrofitModule() = RetrofitModule(BuildConfig.BASE_URL)
    }
    
    class TestApp : App() {
        override fun createRetrofitModule() = RetrofitModule("http://localhost:8080")
    }
    
    
    @Module
    class RetrofitModule(private val baseUrl: String) {
        ...
        provide your Retrofit and OkHttpClients here and use the 'baseUrl'.
        ...
    }
    

    (不确定这是否“编译”;我通常在匕首组件上使用builder() 模式,而不是factory() 模式,但你明白了)。

    这里的模式是为您的应用程序组件或其模块提供“世界边缘”的输入,这些内容需要根据应用程序运行的上下文进行不同的配置(例如作为构建风格,在消费者设备上运行的应用程序与在仪器模式下运行的应用程序等)。例如BuildConfig 值(例如用于网络的基本 URL)、真实或假硬件的接口实现、第 3 方库的接口等。

    【讨论】:

    • 那里有两个问题,一个我可以使用 localhost 与 mockwebserver 相同并且正在工作,这个问题我猜端口不同,这就是为什么不工作,可能是?
    • StuartDTO:Skizo-ozᴉʞS 之前可能已经回答了您的第二个问题。
    • 确实如此。谢谢。现在的问题是我不能使用 Inject 从 TestAppComponent 获取任何东西...
    【解决方案4】:

    a dagger module 为你的Test Class 怎么样,其中有一个ContributeAndroidInjector 并在@Before 方法上执行Inject

    你的TestAppComponent

    @Component(modules = [AndroidInjectionModule::class, TestAppModule::class])
    interface TestAppComponent {
        fun inject(app: TestApp)
    
        @Component.Builder
        interface Builder {
            @BindsInstance
            fun application(application: TestApp): Builder
            fun build(): TestAppComponent
        }
    }
    

    TestAppModule点赞:

    @Module
    interface TestAppModule {
        @ContributesAndroidInjector(modules = [Provider::class])
        fun activity(): MainActivity
    
        @Module
        object Provider {
            @Provides
            @JvmStatic
            fun provideString(): String = "This is test."
    
        }
    
        // Your other dependencies here
    }
    

    Test Class@Before 方法你必须这样做:

    @Before
    fun setUp() {
        val instrumentation = InstrumentationRegistry.getInstrumentation()
        val app = instrumentation.targetContext.applicationContext as TestApp
    
        DaggerTestAppComponent.builder().application(app).build().inject(app)
    
       // Some things other
    }
    

    一件重要的事情,你必须拥有(在build.gradle 模块app):

    kaptAndroidTest "com.google.dagger:dagger-compiler:$version_dagger"
    kaptAndroidTest "com.google.dagger:dagger-android-processor:$version"
    

    现在,当您启动 MainActivity 之类的 Activity 时,Dagger 将从您的 TestAppModule 注入 dependencies,而不是之前的 AppModule

    另外,如果你想@InjectTest Class,可以加:

    fun inject(testClass: TestClass) // On Your TestAppComponent
    

    然后,您可以调用:

    DaggerTestAppComponent.builder().application(app).build().inject(this) // This is on your TestClass
    

    向您的TestClass 注入一些依赖项。

    希望对你有帮助!!

    【讨论】:

    • 看起来我需要什么,你能发一个我必须做的例子,以便我可以试试=
    • 我想是这样,但这意味着我必须添加尽可能多的注入测试?...
    • 不,当您创建 TestModule 时,您可以从 your main moduleinheritance 并在必要的依赖项上执行 override...
    • @StuartDTO 也许您可以查看以下示例:github.com/dinhlamvn/uitestDagger。我已经添加了一个 TestClass 并在那里使用 Dagger 进行一些注入依赖项。
    • 现在你有这个fun inject(test: ExampleInstrumentedTest) 但是如果我想在另一个测试中注入它我不需要它因为我有这个BaseTest?
    【解决方案5】:

    我假设您正在尝试注入 OkHttpClient:

    @Inject
    lateinit var okHttpClient: OkHttpClient
    

    在您的 TestApp 类中,但它失败了。为了让它工作,你需要在你的TestAppComponent中添加一个注入方法,来注入覆盖的TestApp,这样它就变成了:

    @Singleton
    @Component(
        modules = [
            AndroidInjectionModule::class,
            AppModule::class,
            TestRetrofitModule::class,
            AppFeaturesModule::class,
            RoomModule::class]
    )
    interface TestAppComponent : AppComponent {
        @Component.Factory
        interface Factory {
            fun create(@BindsInstance application: App): TestAppComponent
        }
    
        fun inject(testApp: TestApp)
    }
    

    之所以需要这样做,是因为 Dagger 是基于类型的,并且使用要注入/提供的每个类的类型来确定如何在编译时生成代码。在您的情况下,当您尝试注入 TestApp 时,dagger 将注入其超类(App 类),因为它只知道它必须注入 App 类。如果您查看 AndroidInjector 接口(您在 AppComponent 中使用),您会看到它的声明如下:

    public interface AndroidInjector<T> {
        void inject(T instance)
    ....
    }
    

    这意味着它将生成一个方法:

    fun inject(app App)
    

    在 AppComponent 中。这就是为什么@Inject 在您的 App 类中有效,但在您的 TestApp 类中无效,除非您在 TestAppComponent 中明确提供它。

    【讨论】:

    • 所以我必须在每次测试中注入它?我的意思是在@Before 我必须注入它吗?我该如何注入它?我正在尝试使用没有字段注入的 dagger-android 来做到这一点?
    • @StuartDTO 不太清楚你想在哪里注入 OkHttpClient,或者你是否需要注入 OkHttpClient。由于您已经创建了一个 TestRetrofitModule,因此您不需要在任何地方注入 OkHttpClient。所有必要的接线都应在您的 TestRetrofitModule 类中处理。我的回答假设您尝试在 TestApp 类中注入变量,该类不会自动设置为要注入(因为它在 TestApplicationComponent 中没有注入方法调用)。所有注入变量的类都应该在组件中调用注入方法。
    • 但是如果我需要注入诸如 Room 或某些首选项之类的东西,如果我不能使用 @inject,我该如何获取它们?另外,我正在测试中创建 mockWebServer,我认为可以将它作为提供,但给了我 NOMTException
    • 你可以在你的测试类中注入任何你喜欢的东西。但要实现这一点,您应该在 TestAppComponent 中包含一个 inject() 函数并显式注入您的测试类。除非您在 TestAppComponent 中添加注入函数,并在 @Before 方法中显式注入您的测试用例,否则依赖项将不会提供给您的测试类。
    猜你喜欢
    • 2020-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-06
    • 2014-06-07
    • 1970-01-01
    • 1970-01-01
    • 2019-09-10
    相关资源
    最近更新 更多