在 Espresso 中使用 Dagger
Using Dagger with Espresso
我计划在我的应用多模块上创建 Espresso 测试,我即将创建第一个 Espresso 测试,但我看到我的应用上没有 AppComponent
我可以在哪里伪造它。因为我想在我的功能模块上添加测试,所以我将从现在开始在那里创建 TestApp
、TestRunner
。
我的功能模块上有一个 FeatureComponent
,它是通过 ComponentFactory
从 App
注入的,所以我想创建一个 class像这样:
@Component (
dependencies = [ MoreComponents::class],
modules = [ DataSourceModule::class ]
)
interface FeatureOneComponent {
fun activityOneSubComponent(): FeatureOneActivity.Component.Factory
fun activityTwoSubComponent(): FeatureTwoActivity.Component.Factory
@Component.Factory
interface Factory {
fun create(
dependencies
):FeatureOneComponent
}
}
interface FeatureOneProvider {
fun getFeatureOneComponent(): FeatureOneComponent
}
///ACTIVITY
class FeatureOneActivity : AppCompatActivity() {
//this comes from Subcomponent is what I want to MOCK
@Inject lateinit var presenter
//these comes from the factory and I have it mocked
@Inject lateinit var manager
override fun onCreate(){
(applicationContext as FeatureOneProvider).getFeatureOneComponent().activityOneSubComponent().create(this).inject(this)
}
}
@Subcomponent(modules = [ActivityOneModule::class]) <--- THIS I WANT TO MOCK
interface Component {
fun inject(activity: FeatureOneActivity)
@SubComponent.Factory
interface Factory {
fun create(@BindsInstance activity: FeatureOneActivity): Component
}
}
@Module
interface ActivityOneModule {
@Binds
fun bindPresenter(impl: PresenterImpl): Contract.Presenter
}
测试
class MyTestApp : Application(), FeatureOneProvider {
override fun getFeatureOneComponent(): FeatureOneComponent {
return DaggerMockFeatureOneComponent.create()
}
}
@Component(
modules = [MockFeatureOneModules::class]
)
interface MockFeatureOneComponent : FeatureOneComponent {
//I NEED TO MOCK THE SUBCOMPONENT WITH `MockSubcomponent`
}
@Component
object MockFeatureOneModules {
@Provides
fun providesManager() : MyManager = mock(MyManager)
}
//I want to use this module to replace the subcomponent of my activity
@Module
object MockSubcomponent() {
@Provides
fun providesFakePresenter() : FeatureOneContract.Presenter = mock { FeatureOneContract.Presenter::class.java }
}
更好地理解问题
当我 运行 我的测试和我放置调试器点时,我看到除了演示者之外的所有东西都被模拟了,那是因为演示者在
@Subcomponent(modules = [ActivityOneModule::class]
interface Component {
fun inject(activity: FeatureOneActivity)
@SubComponent.Factory
interface Factory {
fun create(@BindsInstance activity: FeatureOneActivity): Component
}
}
@Module
interface ActivityOneModule {
@Binds
fun bindPresenter(impl: PresenterImpl): Contract.Presenter
}
并且在我的测试组件中,我无权“覆盖”这个子组件,所以除了这个子组件之外的所有东西都被模拟了,我需要这个模拟。
据我所知,你有多个模块、组件和子组件,但由于它们的大部分名称与你发布的代码不完全匹配,你也没有发布错误日志,我不得不猜猜哪里出了问题。
相反,像这样的东西怎么样。
public interface SomeComponent {
WhateverClass1 provideWhatever1();
WhateverClass2 provideWhatever2();
WhateverRepository1 provideWhateverRepository1();
SomeOtherComponent getSomeOtherComponent();
// and so on and on
}
然后让您的生产组件看起来像这样:
@SomeComponentSingleton
@Component(modules = { ... },
dependencies = { ... })
public interface SomeProductionScopedComponent extends SomeComponent {
@Component.Builder
interface Builder {
// you know best what needs to go here
SomeProductionScopedComponent build();
}
}
还有这个
@SomeComponentSingleton
@Component(dependencies = SomeComponent.class, modules =
{ ... })
public interface TestSomeComponent {
@Component.Builder
interface Builder {
Builder bindCoreComponent(SomeComponent coreComponent);
@BindsInstance
Builder bindContext(Context context);
TestSomeComponent build();
}
}
然后,假设您使用这些构建器手动实例化组件,只要它们依赖于接口 (SomeComponent
),您应该能够将 ProductionSomeComponent 或 TestSomeComponent 绑定到您的模块。
有道理还是给出了一些提示?
您的示例代码理解起来非常复杂,实际问题也很复杂。
但据我所知,您想为您的功能模块设置 expresso 测试,并且您需要为其设置 dagger 组件。
所以,我可以给你一些指导方针和示例代码,这样你就可以非常简单地遵循和设置你的 Dagger 架构来进行 Espresso 测试。
首先,您需要 setup/create 您的应用程序来进行浓缩咖啡测试,如下所示:
class MyTestApplication : MyApplication() {
//Call this from MyApplication onCreate()
override fun initDaggerGraph() {
component = DaggerTestAppComponent.builder()
.application(this)
.appLifecycle(appLifecycle)
.build()
component.inject(this)
}
}
然后像这样创建您的测试应用组件:
//Add all of your dependent modules in this TestAppModule
@Component(modules = [TestAppModule::class])
interface TestAppComponent : AppComponent {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: Application): Builder
@BindsInstance
fun appLifecycle(appLifecycle: AppLifecycle): Builder
fun build(): TestAppComponent
}
fun inject(activityTest: SomeActivityTest) //Your activity where to inject
}
此外,请确保在启动 activity 时在测试 activity class 中初始化组件,如下所示:
val component = MyApplication.instance.component as TestAppComponent
component.inject(this)
现在您已经完成了所有设置,您的依赖关系应该已经解决,并且您的 espresso 测试应该可以正常工作。
我不知道这是否是最好的主意,但如果我没有误解你的话,你希望这个 Presenter return mock {}
。您可以做的更改是:
- 在您的 TestComponent 中将
interface
更改为 abstract class
- 复制您的子组件并从真实的子组件扩展
@Component(
modules = [MockFeatureOneModules::class]
)
abstract class MockFeatureOneComponent : FeatureOneComponent {
abstract fun subcomponent() : MockComponent.FactoryMock
override fun activityOneSubComponent(): FeatureOneActivity.Component.Factory {
return subcomponent()
}
@Subcomponent
interface MockComponent : FeatureOneActivity.Component {
@Subcomponent.Factory
interface FactoryMock : FeatureOneActivity.Component.Factory {
override fun create....
}
}
}
就是这样,它应该可以工作。
我计划在我的应用多模块上创建 Espresso 测试,我即将创建第一个 Espresso 测试,但我看到我的应用上没有 AppComponent
我可以在哪里伪造它。因为我想在我的功能模块上添加测试,所以我将从现在开始在那里创建 TestApp
、TestRunner
。
我的功能模块上有一个 FeatureComponent
,它是通过 ComponentFactory
从 App
注入的,所以我想创建一个 class像这样:
@Component (
dependencies = [ MoreComponents::class],
modules = [ DataSourceModule::class ]
)
interface FeatureOneComponent {
fun activityOneSubComponent(): FeatureOneActivity.Component.Factory
fun activityTwoSubComponent(): FeatureTwoActivity.Component.Factory
@Component.Factory
interface Factory {
fun create(
dependencies
):FeatureOneComponent
}
}
interface FeatureOneProvider {
fun getFeatureOneComponent(): FeatureOneComponent
}
///ACTIVITY
class FeatureOneActivity : AppCompatActivity() {
//this comes from Subcomponent is what I want to MOCK
@Inject lateinit var presenter
//these comes from the factory and I have it mocked
@Inject lateinit var manager
override fun onCreate(){
(applicationContext as FeatureOneProvider).getFeatureOneComponent().activityOneSubComponent().create(this).inject(this)
}
}
@Subcomponent(modules = [ActivityOneModule::class]) <--- THIS I WANT TO MOCK
interface Component {
fun inject(activity: FeatureOneActivity)
@SubComponent.Factory
interface Factory {
fun create(@BindsInstance activity: FeatureOneActivity): Component
}
}
@Module
interface ActivityOneModule {
@Binds
fun bindPresenter(impl: PresenterImpl): Contract.Presenter
}
测试
class MyTestApp : Application(), FeatureOneProvider {
override fun getFeatureOneComponent(): FeatureOneComponent {
return DaggerMockFeatureOneComponent.create()
}
}
@Component(
modules = [MockFeatureOneModules::class]
)
interface MockFeatureOneComponent : FeatureOneComponent {
//I NEED TO MOCK THE SUBCOMPONENT WITH `MockSubcomponent`
}
@Component
object MockFeatureOneModules {
@Provides
fun providesManager() : MyManager = mock(MyManager)
}
//I want to use this module to replace the subcomponent of my activity
@Module
object MockSubcomponent() {
@Provides
fun providesFakePresenter() : FeatureOneContract.Presenter = mock { FeatureOneContract.Presenter::class.java }
}
更好地理解问题
当我 运行 我的测试和我放置调试器点时,我看到除了演示者之外的所有东西都被模拟了,那是因为演示者在
@Subcomponent(modules = [ActivityOneModule::class]
interface Component {
fun inject(activity: FeatureOneActivity)
@SubComponent.Factory
interface Factory {
fun create(@BindsInstance activity: FeatureOneActivity): Component
}
}
@Module
interface ActivityOneModule {
@Binds
fun bindPresenter(impl: PresenterImpl): Contract.Presenter
}
并且在我的测试组件中,我无权“覆盖”这个子组件,所以除了这个子组件之外的所有东西都被模拟了,我需要这个模拟。
据我所知,你有多个模块、组件和子组件,但由于它们的大部分名称与你发布的代码不完全匹配,你也没有发布错误日志,我不得不猜猜哪里出了问题。
相反,像这样的东西怎么样。
public interface SomeComponent {
WhateverClass1 provideWhatever1();
WhateverClass2 provideWhatever2();
WhateverRepository1 provideWhateverRepository1();
SomeOtherComponent getSomeOtherComponent();
// and so on and on
}
然后让您的生产组件看起来像这样:
@SomeComponentSingleton
@Component(modules = { ... },
dependencies = { ... })
public interface SomeProductionScopedComponent extends SomeComponent {
@Component.Builder
interface Builder {
// you know best what needs to go here
SomeProductionScopedComponent build();
}
}
还有这个
@SomeComponentSingleton
@Component(dependencies = SomeComponent.class, modules =
{ ... })
public interface TestSomeComponent {
@Component.Builder
interface Builder {
Builder bindCoreComponent(SomeComponent coreComponent);
@BindsInstance
Builder bindContext(Context context);
TestSomeComponent build();
}
}
然后,假设您使用这些构建器手动实例化组件,只要它们依赖于接口 (SomeComponent
),您应该能够将 ProductionSomeComponent 或 TestSomeComponent 绑定到您的模块。
有道理还是给出了一些提示?
您的示例代码理解起来非常复杂,实际问题也很复杂。 但据我所知,您想为您的功能模块设置 expresso 测试,并且您需要为其设置 dagger 组件。
所以,我可以给你一些指导方针和示例代码,这样你就可以非常简单地遵循和设置你的 Dagger 架构来进行 Espresso 测试。
首先,您需要 setup/create 您的应用程序来进行浓缩咖啡测试,如下所示:
class MyTestApplication : MyApplication() {
//Call this from MyApplication onCreate()
override fun initDaggerGraph() {
component = DaggerTestAppComponent.builder()
.application(this)
.appLifecycle(appLifecycle)
.build()
component.inject(this)
}
}
然后像这样创建您的测试应用组件:
//Add all of your dependent modules in this TestAppModule
@Component(modules = [TestAppModule::class])
interface TestAppComponent : AppComponent {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: Application): Builder
@BindsInstance
fun appLifecycle(appLifecycle: AppLifecycle): Builder
fun build(): TestAppComponent
}
fun inject(activityTest: SomeActivityTest) //Your activity where to inject
}
此外,请确保在启动 activity 时在测试 activity class 中初始化组件,如下所示:
val component = MyApplication.instance.component as TestAppComponent
component.inject(this)
现在您已经完成了所有设置,您的依赖关系应该已经解决,并且您的 espresso 测试应该可以正常工作。
我不知道这是否是最好的主意,但如果我没有误解你的话,你希望这个 Presenter return mock {}
。您可以做的更改是:
- 在您的 TestComponent 中将
interface
更改为abstract class
- 复制您的子组件并从真实的子组件扩展
@Component(
modules = [MockFeatureOneModules::class]
)
abstract class MockFeatureOneComponent : FeatureOneComponent {
abstract fun subcomponent() : MockComponent.FactoryMock
override fun activityOneSubComponent(): FeatureOneActivity.Component.Factory {
return subcomponent()
}
@Subcomponent
interface MockComponent : FeatureOneActivity.Component {
@Subcomponent.Factory
interface FactoryMock : FeatureOneActivity.Component.Factory {
override fun create....
}
}
}
就是这样,它应该可以工作。