Using Daggger to mock SubComponent – Part 2

In the last tutorial we created a SubComponent to insert NetworkApi into an activity

Now if we use a UI testing frameworks for e.g. espresso, we know that we

Dagger-Courtsey pixabay.com
Dagger

need to mock a lot of things that interact with external system.

NetworkApi is one of the call that needs to be mocked because we cannot make actual network call in Api when doing just UI testing.

Mocking SubComponent is slightly more difficult than mocking Components.

Let’s see how to do that.

First we create a test , where we will call espresso methods to check the inputs etc.

<br data-mce-bogus="1">
<pre>/**
 * Created by AKS on 10/18/2017.
 */
@RunWith(AndroidJUnit4.class)
@CustomScope
public class MockActivityTest {

    @Inject
    NetworkApi networkApi;
    @Rule
    public ActivityTestRule<MainActivity> activityRule = new ActivityTestRule<>(
            MainActivity.class,
            true,     // initialTouchMode
            false);   // launchActivity. False so we can customize the intent per test method

    @Before
    public void setUp() {

        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
        CustomApplication app
                = (CustomApplication) instrumentation.getTargetContext().getApplicationContext();
        MockSubAppComponent component = (MockSubAppComponent) app.getAppSubComponent();
        component.inject(this);

    }

    @Test
    public void testThis(){
        activityRule.launchActivity(new Intent());
    }
}</pre>

 

There are a few important points . We need to inject the mock into our test class

@Inject
NetworkApi networkApi;

Next, we create a rule where we do not start activity automatically


@Rule
public ActivityTestRule<MainActivity> activityRule = new ActivityTestRule<>(
MainActivity.class,
true, // initialTouchMode
false); // launchActivity. False so we can customize the intent per test method

And we leave out the setup for now .

Let us instead focus on module that will be used to provide mock NetworkApi


@Module
public class MockNetworkApiModule {

@CustomScope
@Provides
NetworkApi provideNetworkApi(){

return mock(NetworkApi.class);
}
}

The MockAppComponent will subclass AppComponent because where ever we can inject base class, we can safely inject subclass too


@Component
public interface MockAppComponent extends AppComponent {
MockSubAppComponent.Builder mockSubComponentBuilder();
}

The MockComponent looks similar to AppComponent

Then we have the MockSubComponent which extends SubComponent


@CustomScope
@Subcomponent(modules = {MockNetworkApiModule.class})
public interface MockSubAppComponent extends AppSubComponent{

public void inject(MockActivityTest mainActivity);

@Subcomponent.Builder
public interface Builder{

MockSubAppComponent subComponent();
}
}

We create the MockApplication class which subclasses CustomApplication class


public class MockApplication extends CustomApplication {

&nbsp;

@Override
protected MockSubAppComponent createSubComponent() {
return DaggerMockAppComponent
.builder()
.build()
.mockSubComponentBuilder()
.subComponent();
}
}

and here you can see how I built the mock subcomponent ( actually not very different than SubComponent construction.

Back to activity test method


@Before
public void setUp() {

Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
CustomApplication app
= (CustomApplication) instrumentation.getTargetContext().getApplicationContext();
MockSubAppComponent component = (MockSubAppComponent) app.getAppSubComponent();
component.inject(this);

}

We just get the MockApplication ( we will see how ) and then we use it to get subcomponent

Now how do we get MockApplication instead of CustomApplication. We create an object called as MockTestRunner.


public class MockTestRunner extends AndroidJUnitRunner {
@Override
public Application newApplication(ClassLoader cl, String className, Context context)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return super.newApplication(cl, MockApplication.class.getName(), context);
}
}

This runner class will provide us with MockApplication class .

But how do we cause Android to use this runner for testing.

Open build.gradle and

change the parameter for runner as follows


defaultConfig {
applicationId "com.instanect.testdaggersubcomponent"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "com.instanect.testdaggersubcomponent.MockTestRunner"
}

make sure that you use the right package name .

That’s it !!

Delete any original configuration you have created because it does not pick up our runner. Run with new configuration.

Don’t forget to use same @CustomScope wherever relevant otherwise you will receive different objects in mocking and in actual activity.

Now write your espresso code knowing that you have successfully mocked Dagger SubComponent in your work 🙂

 

Leave a Reply

Your email address will not be published. Required fields are marked *