(DAGGER-ANDROID) 不能在 Espresso 测试中使用 @Inject 并且不能使用 mockWebServer

(DAGGER-ANDROID) Can not use @Inject on an Espresso Test and can not use mockWebServer

我正在尝试创建 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 的那个,问题是我必须 link mockWebServer 和 Retrofit。

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

fun create(@BindsInstance application: TestApp): TestAppComponent

fun create(@BindsInstance application: App): TestAppComponent

当我尝试做类似的事情时,我不会创建两种类型的应用程序组件,只会创建一种。我根据是实际的 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'.
    ...
}

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

此处的模式是为您的应用程序组件或其模块提供 'edge-of-the-world' 的输入,这些内容需要根据应用程序 运行 的上下文进行不同的配置(构建风格、消费者设备上的应用 运行ning 与检测模式下的 运行ning 等上下文)。示例是 BuildConfig 值(例如用于网络的基本 URL)、真实或假硬件的接口实现、第 3 方库的接口等。

我最近在mockWebServer遇到了同样的问题,你需要做的是设置一个断点,看看有什么错误,在我的情况下,我把它放在我的BaseRepository我所在的地方打电话,发现异常是:

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

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

android:usesCleartextTraffic="true"

但您可能需要使用其他方法,您可以查看

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

@Inject
lateinit var okHttpClient: OkHttpClient

在您的 TestApp class 中,它失败了。为了让它工作,你需要在你的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 是基于类型的,并且使用每个 class 的类型为 injected/provided 来确定如何在编译时生成代码。在您的情况下,当您尝试注入 TestApp 时,dagger 将注入它的 superclass(App class),因为它只知道它必须注入 App class。如果您查看 AndroidInjector 接口(您在 AppComponent 中使用的接口),您会看到它的声明如下:

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

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

fun inject(app App)

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

你的 Test Class 中有 ContributeAndroidInjectora dagger module 怎么样,然后用 @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"

现在,当您启动 Activity 时,例如 MainActivity,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注入一些依赖项。

希望对您有所帮助!!