Let’s play with different configurations and tests.

Update: There is a second part of this post: Let’s play with different configurations and tests… in a multi-module app

Recently I came across Android injectors for Dagger 2. As I have always used dependent components in my Dagger configurations and only sometimes Subcomponents, I decided to create a small pet project where every possibility is tried.

I won’t explain here how Dagger 2 works or the different configurations we can use, There are plenty of posts about it, the idea of this post is to provide a repository to play with.

Specifically I wanted a project with:

App level singletons.

Scope level singletons.

Constructor and property injection samples.

Instrumentation test with module substitution.

Unit tests with and without Android framework dependencies.

And I wanted to try it with the following configurations:

Dependent components.

Subcomponents

Subcomponents and builders

Android injectors

Sample app

The project can be found in Github.

There are four different branches for each of the above configurations.

The application consist in three activities (Main, Detail and Another) each one with a property injected Presenter. Each presenter has a instance of the Navigator and different collaborators injected by constructor.

I have added two collaborator classes: AppCollaborator simulates something needed at application level, like a Repository. It must be singleton and we must be able to substitute it for a mocked version during instrumentation tests. ActCollaborator is a scoped singleton and in the real world it would be any Activity or Service collaborator class.

A scoped injected class means that that class will exist only during the life of the scoped component. In this example it means each activity life.

And finally, the Navigator is the class in charge of navigating between activities. It has the ApplicationContext, both collaborators and the current activity injected. Only the later is really needed but logging all those classes in Navigator will allow us to understand instance lifecycles.

In order to get familiar with the lifecycle and the different configurations each branch README file will propose several exercises:

1- Open the app, navigate to detail, another and check the logs: Context and AppCollaborator share the same instance in Navigator logs, why? Activity and ActCollaborator are different instances in Navigator logs, why? ActCollaborator share the same instance between DetailPresenter and the second Navigator log, why? ...

The DI system

We want to maintain things simple, so there are only one ActivityComponent that can be used in every activity. We instantiate the component in a BaseActivity, but we still do the injections in each top level activity:

@PerActivity

@Component(

dependencies = ApplicationComponent.class,

modules = ActivityModule.class

)

public interface ActivityComponent {



void inject(MainActivity mainActivity);



void inject(DetailActivity detailActivity);



void inject(AnotherActivity anotherActivity);



//Exposed to sub-graphs.

BaseActivity activity();

}

BaseActivity:

public class BaseActivity extends AppCompatActivity {

private ActivityComponent activityComponent;



@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

this.activityComponent = DaggerActivityComponent.builder()

.applicationComponent(getApplicationComponent())

.activityModule(new ActivityModule(this))

.build();

}

MainActivity:

public class MainActivity extends BaseActivity {



@Inject

MainPresenter presenter;



...



@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

getActivityComponent().inject(this);

...

}

}

This trick can be done using Components or Subcomponents, but I have not been able of doing it using Android Injectors. Here we will need an ActivityComponent for each activity if you need that activity to be injectable in collaborators, like the Navigator in this example.

@PerActivity

@Subcomponent(modules = {ActivityModule.class, MainActivityModule.class})

public interface MainActivityComponent extends AndroidInjector<MainActivity> {



@Subcomponent.Builder

abstract class Builder extends AndroidInjector.Builder<MainActivity> {

}

}

Apart from this, I cannot see any real advantage or disadvantage of each method. Maybe Dependent components give us better fine control of what is made available to inner components from external ones. Or maybe the use of builders with subcomponents make the code more clean.

Android injectors pretend to be more SOLID compliant making the Activities unaware of the parent components. I don’t think it is really necessary, Activities won’t be SOLID compliant in any case, and it complicates things unnecessarily.

But judge yourself: Open a class, select the different branches and see how that class is for each configuration.

Unit tests:

Unit testing a presenter is easy: We simply instantiate the class with mock collaborators. That’s why injection by constructor is so powerful:

public class MainPresenterShould {



@Mock

Navigator navigator;

@Mock

IAppCollaborator collaborator;



private MainPresenter presenter;



@Before

public void setup() {

MockitoAnnotations.initMocks(this);

presenter = new MainPresenter(navigator, collaborator);

}

Testing the Navigator class is slightly more difficult. It is Android framework aware (There are Logs, Context, Activities and Intents on it). So we need to mock the Android framework. The easiest way to do so is running the test with Robolectric test runner:

@RunWith(RobolectricTestRunner.class)

public class NavigatorShould {

...

}

Instrumentation tests:

In order to do instrumentation tests, we usually need to substitute the data source for a mocked one. We will simulate this substituting the AppCollaborator class. This class provides the title for the MainActivity and we will want a different title while testing with Espresso.

To do so we need to extend AndroidJUnitRunner with our own TestRunner that will instantiate the TestAndroidApplication class that extends the AndroidApplication one (Our Application instance) . This way, we can use a different application component, the TestApplicationComponent, that will use TestApplicationModule instead ApplicationModule.

Seems complicated? It is not. Take a moment to follow my previous explanation in Android Studio or the Github repo. On a real world, our Application component will use several modules, and now we will be able to change only those we need to have a different behaviour under tests.

In this sample case both AppCollaborator and TestAppCollaborator extend from IAppCollaborator (Sorry for the old naming, but I wanted to leave it clear that this is an interface), so the module only have to change the instance returned:

@Provides

@Singleton

IAppCollaborator provideAppCollaborator() {

return new TestAppCollaborator();

}

Hope this playground helps you test different dagger configurations and understand the different behaviours and testing systems.