【问题标题】:Using Dagger with Espresso将 Dagger 与 Espresso 搭配使用
【发布时间】:2021-04-14 15:06:43
【问题描述】:

我计划在我的应用程序多模块上创建 Espresso 测试,我即将创建第一个 Espresso 测试,但我看到的是在我的应用程序上我没有 AppComponent 在哪里我可以伪造它。由于我想在我的功能模块上添加测试,我将从现在开始在那里创建TestAppTestRunner

我的功能模块上有一个FeatureComponent,它是通过ComponentFactoryApp 注入的,所以我想创建一个这样的类:

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

为了更好地理解问题

当我运行我的测试并放置一个调试器点时,我看到除了 Presenter 之外的所有东西都被模拟了,那是因为 Presenter 在里面

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

在我的测试组件中,我无权“覆盖”这个子组件,所以一切都被模拟了,但这个子组件我需要这个模拟。

【问题讨论】:

  • 您不能覆盖组件。您需要在测试中使用“测试组件”,在生产代码中使用“真实组件”。但是,从您发布的代码中,很难说您在匕首设置中出了什么问题。
  • 是的,但是如果我的子组件有modules=[...],我该如何伪造这些?让我用清晰的代码编辑我的问题
  • @Shark 编辑了我的问题,问题是如何“覆盖”我的生产子组件上的模块并在我的 mockSubcomponent 上使用模拟模块
  • 好吧,您的生产子组件没有使用硬编码的模块列表吗?让我尝试发布一些东西,看看你是否抓住了这个想法
  • 您是否将FeatureOneComponent 绑定到ApplicationComponent?你确定它绑定的是 TestF1Component 而不是 ProductionF1Component 吗?

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


【解决方案1】:

我不知道这是否是最好的主意,但如果我没有误解您,您希望此演示者返回 mock {}。你可以做的改变是:

  1. 在您的 TestComponent 中将 interface 更改为 abstract class
  2. 复制您的子组件并从真实组件扩展
@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....
       }
    }
}

就是这样,它应该可以工作。

【讨论】:

    【解决方案2】:

    据我所知,您有多个模块、组件和子组件,但是由于它们的大多数名称在您发布的代码中并不完全匹配,也不是您发布的错误日志,我不得不猜测是什么哪里出错了。

    相反,这样的事情怎么样。

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

    那么,鉴于您在某种程度上使用这些 Builder 手动实例化组件,只要它们依赖于接口 (SomeComponent),您应该能够将 ProductionSomeComponent 或 TestSomeComponent 绑定到您的模块中。

    有道理还是给出了一些提示?

    【讨论】:

    • @StuartDTO 基本上,TestComponent 将具有提供模拟或测试变体(某些)生产对应物的模块,这只是显示了您应该前进的总体方向。这不是分步指南或问题的解决方案,只是展示了如何绕过您当前遇到的问题的想法 - 绑定生产模块而不是组件中的测试模块。
    • 让我用匹配的代码更新问题。
    • 你能重新检查一下这个问题吗?我已经对其进行了编辑,它可以编译并且可以工作,但是我缺少模拟子组件模块的部分...
    【解决方案3】:

    您的示例代码非常复杂,难以理解和实际问题。 但据我所知,您想为您的功能模块设置 expresso 测试,并且您需要为它设置 dagger 组件。

    因此,我可以为您提供一些指导方针和示例代码,以便您可以非常简单地遵循和设置您的 dagger 架构以进行 espresso 测试。

    首先,您需要像这样设置/创建用于 espresso 测试的应用程序:

    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
    }
    

    此外,请确保在启动活动时在您的测试活动类中初始化您的组件,如下所示:

    val component = MyApplication.instance.component as TestAppComponent
    component.inject(this)
    

    现在您已完成所有设置,您的依赖项应该可以解决,并且您的 espresso 测试应该可以工作。

    【讨论】:

    • 这是另一种方式,我不想在我的测试中注入,我正在尝试创建一个 TestSubcomponent,检查我的问题 @Subcomponent(modules=[ActivityOneModule::class] 我希望这个模块模拟,但我不能模拟那个子组件,其他的东西都被模拟了。
    猜你喜欢
    • 2020-01-23
    • 2020-09-17
    • 1970-01-01
    • 2018-11-09
    • 2017-06-24
    • 1970-01-01
    • 2016-04-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多