4

Using AndroidInjector and Subcomponents make it impossible to inject the activity scoped objected into the Test class for Espresso.

Previously with Application level components and activity components you had the ability call inject() for test classes that were not activities as long as you create a test component that inherited the activity component.

Example:

Activity Component

@ActivityScope
@Component(
    dependencies = ApplicationComponent.class,
    modules = {
            NowPlayingActivityModule.class
    })
public interface NowPlayingActivityComponent {
    void inject(NowPlayingActivity activity);
}

Test Class Component

@ActivityScope
@Component(
    dependencies = TestApplicationComponent.class,
    modules = {
            TestNowPlayingActivityModule.class,
            ActivityModule.class
    })
 public interface TestNowPlayingActivityComponent extends NowPlayingActivityComponent {
    void inject(NowPlayingActivityTest nowPlayingActivityTest);
}

Test Module

@Module
public class TestNowPlayingActivityModule {
    private NowPlayingActivityModule nowPlayingActivityModule;

    public TestNowPlayingActivityModule(NowPlayingActivityModule nowPlayingActivityModule) {
        this.nowPlayingActivityModule = nowPlayingActivityModule;
    }

    @Provides
    @ActivityScope
    public ServiceGateway providesServiceGateway(ServiceApi serviceApi) {
        return nowPlayingActivityModule.providesServiceGateway(serviceApi);
    }

    @Provides
    @ActivityScope
    public NowPlayingPresenter providesNowPlayingPresenter(NowPlayingInteractor nowPlayingInteractor) {
        //In order to make sure espresso idles the view checks, we put the IdlingResource on the presenter.
        return Mockito.spy(new NowPlayingPresenterImpl_IdlingResource(nowPlayingActivityModule.getNowPlayingViewModel(),
            nowPlayingInteractor));
    }
}

In Test Class

TestNowPlayingActivityComponent mockNowPlayingActivityComponent = DaggerTestNowPlayingActivityComponent.builder()
            .testApplicationComponent((TestApplicationComponent) mvpExampleApplication.getComponent())
            .testNowPlayingActivityModule(new TestNowPlayingActivityModule(nowPlayingActivityModule))
            .build();

mockNowPlayingActivityComponent.inject((NowPlayingActivity) activity);
mockNowPlayingActivityComponent.inject(NowPlayingActivityTest.this);

How are people getting access to the activity modules that are auto generated and using them in espresso UI Test? I want to have access to objects like "ServiceGateway" & "NowPlayingPresenter" above and utilize them in the test. Either mock, spy, or idling resource. My idling resource in the above example is the "NowPlayingPresenter" concrete implementation that I pass to espresso during each individual test.

LEO
  • 2,572
  • 1
  • 26
  • 31
  • If you are still to decide which approach to take check my solution [here](https://stackoverflow.com/a/45545442/8326724) – The Riddler Aug 07 '17 at 11:12

2 Answers2

0

I managed to solve this the other day with a little hacky approach using a custom Test Runner. First thing is to have a TestRunner for faking the Android Application. Now you can simply extend your main app class and override onCreate(), injecting a component built especially for testing that returns mock instances instead of the real ones.

mcassiano
  • 315
  • 3
  • 9
0

I find myself in a similar situation. Here's my question Espresso testing with Dagger 2 and custom scopes . It is really tricky to inject mocks when testing with Espresso.

The approach that mcassiano suggested was the first idea that came to mind for solving this issue. However, I did not take this approach because of the code overhead one has to create. I would be interested to see what an Espresso test created by mcassiano looks like. I assume that you write an ActivityTestRule for the Activity which you overrode in order to inject the mocks.

In an ideal scenario of using Dagger 2 I would like to avoid the following:

  • Have an Application component which is aware of all other components
  • Have an Application component provide all dependencies to the entire app

The latter I see as a code smell, because if we ended up in any of the above situations we'll have a giant App component and module which are tightly coupled with every app feature.

What should happen instead is to have every feature responsible for injecting it's own dependencies.

With regards to finding a solution for injecting mocks I have the ideas below, which I did not implement, because I do not want to mix test with production code. However, would appreciate if you guys share what you think.

  1. Introduce a component builder that is only initialised as part of the test setup and in the Activity code go with this setup if initialised, otherwise set up the real component.
  2. In each feature module check the build type and provide a mock if the build type = espresso else a real object
The Riddler
  • 107
  • 8