(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
中有 ContributeAndroidInjector
的 a 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
。
此外,如果你想@Inject
到Test Class
,你可以添加:
fun inject(testClass: TestClass) // On Your TestAppComponent
然后,您可以调用:
DaggerTestAppComponent.builder().application(app).build().inject(this) // This is on your TestClass
向您的TestClass
注入一些依赖项。
希望对您有所帮助!!
我正在尝试创建 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
中有 ContributeAndroidInjector
的 a 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
。
此外,如果你想@Inject
到Test Class
,你可以添加:
fun inject(testClass: TestClass) // On Your TestAppComponent
然后,您可以调用:
DaggerTestAppComponent.builder().application(app).build().inject(this) // This is on your TestClass
向您的TestClass
注入一些依赖项。
希望对您有所帮助!!