简体   繁体   中英

Using Dagger with Espresso

I'm planning to create Espresso tests on my app multi-module, and I'm about to create the first Espresso test, but what I'm seeing is that on my app I do not have an AppComponent where I can fake it. Since I want to add the test on my feature-module, I'll create the TestApp , TestRunner there from now.

What I have on my feature-module is a FeatureComponent that is injected via ComponentFactory from the App , so what I thought is to create a class like this:

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

TEST

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

To better understand the problem

When I run my test and I put a debugger point I see everything is mocked but the Presenter, and that's because the presenter is in

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

And in my test component I don't have access to "override" this subcomponent so everything is mocked but this subcomponent and I need this mocked.

I don't know if that's the best idea but if I did not misunderstand you you want this Presenter to return a mock {} . The changes you could do are:

  1. In your TestComponent change interface to abstract class
  2. Duplicate your subcomponent and extends from the real one
@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....
       }
    }
}

And that's it, it should work.

So as far as i get it, you have multiple modules, components and subcomponents, but since most of their names don't quite match up in the code you posted, nor you posted the error log, i have to guess whats going wrong where.

Instead, how about something like this.

public interface SomeComponent {

    WhateverClass1 provideWhatever1();
    WhateverClass2 provideWhatever2();
    WhateverRepository1 provideWhateverRepository1();
    SomeOtherComponent getSomeOtherComponent();
   // and so on and on
}

and then have your production component look something like this:

@SomeComponentSingleton
@Component(modules = { ... },
        dependencies = { ... })
public interface SomeProductionScopedComponent extends SomeComponent {

    @Component.Builder
    interface Builder {
       // you know best what needs to go here

       SomeProductionScopedComponent build();
    }
}

and this

@SomeComponentSingleton
@Component(dependencies = SomeComponent.class, modules =
        { ... })
public interface TestSomeComponent {

    @Component.Builder
    interface Builder {
        Builder bindCoreComponent(SomeComponent coreComponent);

        @BindsInstance
        Builder bindContext(Context context);

        TestSomeComponent build();
    }
}

then, given that you're somewhat manually instantiating the components with these Builders, as long as they depend on the interface ( SomeComponent ), you should be able to bind a ProductionSomeComponent or a TestSomeComponent into your module.

Makes sense or gives some hint?

Your example code is pretty complex to understand and the actual problem. But what I understand you want to setup expresso test for your feature module and you need to setup dagger component for it.

So, I can give you some guidelines and example code so that you can follow and setup your dagger architecture for your espresso test very simply.

First of all, you need setup/create your App for espresso test like this:

class MyTestApplication : MyApplication() {

    //Call this from MyApplication onCreate()
    override fun initDaggerGraph() { 
        component = DaggerTestAppComponent.builder()
            .application(this)
            .appLifecycle(appLifecycle)
            .build()
        component.inject(this)
    }
}

Then create your Test app component like 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
}

Also, make sure to initialize your component in your Test activity class when launching the activity like this:

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

Now you have done all the setup and your dependency should resolve as well as your espresso test should work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM