1. Code
  2. Coding Fundamentals
  3. Design Patterns

Testing and Dependency Injection With Model View Presenter on Android

In the final part of this series on the Model View Presenter pattern, we explore MVP in the light of testing. We also discuss how the MVP pattern can be implemented with dependency injection using Dagger 2.
Scroll to top
This post is part of a series called How to Adopt Model View Presenter on Android.
How to Adopt Model View Presenter on Android

We explored the concepts of the Model View Presenter pattern in the first part of this series and we implemented our own version of the pattern in the second part. It's now time to dig a little deeper. In this tutorial, we focus on the following topics:

  • setting up the test environment and writing unit tests for the MVP classes
  • implementing the MVP pattern using dependency injection with Dagger 2
  • we discuss common problems to avoid when using MVP on Android

1. Unit Testing

One of the biggest advantages of adopting the MVP pattern is that it simplifies unit testing. So let's write tests for the Model and Presenter classes we created and implemented in the last part of this series. We will run our tests using Robolectric, a unit test framework that provides many useful stubs for Android classes. To create mock objects, we will use Mockito, which allows us to verify if certain methods were called.

Step 1: Setup

Edit the build.gradle file of your app module and add the following dependencies.

1
dependencies { 
2
 //…  
3
 testCompile 'junit:junit:4.12' 
4
 // Set this dependency if you want to use Hamcrest matching  
5
 testCompile 'org.hamcrest:hamcrest-library:1.1' 
6
 testCompile "org.robolectric:robolectric:3.0" 
7
 testCompile 'org.mockito:mockito-core:1.10.19' 
8
} 

Inside the project's src folder, create the following folder structure test/java/[package-name]/[app-name]. Next, create a debug configuration to run the test suite. Click Edit Configurations… at the top.

Add a ConfigurationAdd a ConfigurationAdd a Configuration

Click the + button and select JUnit from the list.

Select JUnitSelect JUnitSelect JUnit

Set Working directory to $MODULE_DIR$.

Configure the ConfigurationConfigure the ConfigurationConfigure the Configuration

We want this configuration to run all the unit tests. Set Test kind to All in package and enter the package name in the Package field.

Configure the ConfigurationConfigure the ConfigurationConfigure the Configuration

Step 2: Testing the Model

Let's begin our tests with the Model class. The unit test runs using RobolectricGradleTestRunner.class, which provides the resources needed to test Android specific operations. It is important to annotate @Cofing with the following options:

1
@RunWith(RobolectricGradleTestRunner.class) 
2
// Change what is necessary for your project  
3
@Config(constants = BuildConfig.class, sdk = 21, manifest = "/src/main/AndroidManifest.xml") 
4
public class MainModelTest { 
5
 // write the tests  
6
} 

We want to use a real DAO (data access object) to test if the data is being handled correctly. To access a Context, we use the RuntimeEnvironment.application class.

1
private DAO mDAO; 
2
// To test the Model you can just  
3
// create the object and and pass  
4
// a Presenter mock and a DAO instance  
5
@Before 
6
public void setup() { 
7
 // Using RuntimeEnvironment.application will permit  
8
 // us to access a Context and create a real DAO  
9
 // inserting data that will be saved temporarily  
10
 Context context = RuntimeEnvironment.application; 
11
 mDAO = new DAO(context); 
12
 // Using a mock Presenter will permit to verify  
13
 // if certain methods were called in Presenter  
14
 MainPresenter mockPresenter = Mockito.mock(MainPresenter.class); 
15
 // We create a Model instance using a construction that includes  
16
 // a DAO. This constructor exists to facilitate tests  
17
 mModel = new MainModel(mockPresenter, mDAO); 
18
 // Subscribing mNotes is necessary for tests methods  
19
 // that depends on the arrayList  
20
 mModel.mNotes = new ArrayList<>(); 
21
 // We’re reseting our mock Presenter to guarantee that  
22
 // our method verification remain consistent between the tests  
23
 reset(mockPresenter); 
24
} 

It is now time to test the Model's methods.

1
// Create Note object to use in the tests  
2
private Note createNote(String text) { 
3
 Note note = new Note(); 
4
 note.setText(text); 
5
 note.setDate("some date"); 
6
 return note; 
7
} 
8
// Verify loadData  
9
@Test 
10
public void loadData(){ 
11
 int notesSize = 10; 
12
 // inserting data directly using DAO  
13
 for (int i =0; i<notesSize; i++){ 
14
 mDAO.insertNote(createNote("note_" + Integer.toString(i))); 
15
 } 
16
 // calling load method  
17
 mModel.loadData(); 
18
 // verify if mNotes, an ArrayList that receives the Notes  
19
 // have the same size as the quantity of Notes inserted  
20
 assertEquals(mModel.mNotes.size(), notesSize); 
21
} 
22
 
23
// verify insertNote  
24
@Test 
25
public void insertNote() { 
26
 int pos = mModel.insertNote(createNote("noteText")); 
27
 assertTrue(pos > -1); 
28
} 
29
 
30
// Verify deleteNote  
31
@Test 
32
public void deleteNote() { 
33
 // We need to add a Note in DB  
34
 Note note = createNote("testNote"); 
35
 Note insertedNote = mDAO.insertNote(note); 
36
 // add the same Note inside mNotes ArrayList  
37
 mModel.mNotes = new ArrayList<>(); 
38
 mModel.mNotes.add(insertedNote); 
39
 // verify if deleteNote returns the correct results  
40
 assertTrue(mModel.deleteNote(insertedNote, 0)); 
41
 Note fakeNote = createNote("fakeNote"); 
42
 assertFalse(mModel.deleteNote(fakeNote, 0)); 
43
} 

You can now run the Model test and check the results. Feel free to test other aspects of the class.

Step 3: Testing the Presenter

Let's now focus on testing the Presenter. We also need Robolectric for this test to make use of several Android classes, such as AsyncTask. The configuration is very similar to the Model test. We use View and Model mocks to verify method calls and define return values.

1
@RunWith(RobolectricGradleTestRunner.class) 
2
@Config(constants = BuildConfig.class, sdk = 21, manifest = "/src/main/AndroidManifest.xml") 
3
public class MainPresenterTest { 
4
 private MainPresenter mPresenter; 
5
 private MainModel mockModel; 
6
 private MVP_Main.RequiredViewOps mockView; 
7
 // To test the Presenter you can just  
8
 // create the object and pass the Model and View mocks  
9
 @Before 
10
 public void setup() { 
11
 // Creating the mocks  
12
 mockView = Mockito.mock( MVP_Main.RequiredViewOps.class ); 
13
 mockModel = Mockito.mock( MainModel.class, RETURNS_DEEP_STUBS ); 
14
 // Pass the mocks to a Presenter instance  
15
 mPresenter = new MainPresenter( mockView ); 
16
 mPresenter.setModel(mockModel); 
17
 // Define the value to be returned by Model  
18
 // when loading data  
19
 when(mockModel.loadData()).thenReturn(true); 
20
 reset(mockView); 
21
 } 
22
} 

To test the Presenter's methods, let's begin with the clickNewNote() operation, which is responsible for creating a new note and register it in the database using an AsyncTask.

1
@Test 
2
public void testClickNewNote() { 
3
 // We need to mock a EditText  
4
 EditText mockEditText = Mockito.mock(EditText.class, RETURNS_DEEP_STUBS); 
5
 // the mock should return a String  
6
 when(mockEditText.getText().toString()).thenReturn(Test_true"); 
7
 // we also define a fake position to be returned  
8
 // by the insertNote method in Model  
9
 int arrayPos = 10; 
10
 when(mockModel.insertNote(any(Note.class))).thenReturn(arrayPos); 
11
 
12
 mPresenter.clickNewNote(mockEditText); 
13
 verify(mockModel).insertNote(any(Note.class)); 
14
 verify(mockView).notifyItemInserted( eq(arrayPos+1) ); 
15
 verify(mockView).notifyItemRangeChanged(eq(arrayPos), anyInt()); 
16
 verify(mockView, never()).showToast(any(Toast.class)); 
17
} 

We could also test a scenario in which the insertNote() method returns an error.

1
@Test 
2
public void testClickNewNoteError() { 
3
 EditText mockEditText = Mockito.mock(EditText.class, RETURNS_DEEP_STUBS); 
4
 when(mockModel.insertNote(any(Note.class))).thenReturn(-1); 
5
 when(mockEditText.getText().toString()).thenReturn("Test_false"); 
6
 when(mockModel.insertNote(any(Note.class))).thenReturn(-1); 
7
 mPresenter.clickNewNote(mockEditText); 
8
 verify(mockView).showToast(any(Toast.class)); 
9
} 

Finally, we test deleteNote() method, considering both a successful and an unsuccessful result.

1
@Test 
2
public void testDeleteNote(){ 
3
 when(mockModel.deleteNote(any(Note.class), anyInt())).thenReturn(true); 
4
 int adapterPos = 0; 
5
 int layoutPos = 1; 
6
 mPresenter.deleteNote(new Note(), adapterPos, layoutPos); 
7
 verify(mockView).showProgress(); 
8
 verify(mockModel).deleteNote(any(Note.class), eq(adapterPos)); 
9
 verify(mockView).hideProgress(); 
10
 verify(mockView).notifyItemRemoved(eq(layoutPos)); 
11
 verify(mockView).showToast(any(Toast.class)); 
12
} 
13
 
14
@Test 
15
public void testDeleteNoteError(){ 
16
 when(mockModel.deleteNote(any(Note.class), anyInt())).thenReturn(false); 
17
 int adapterPos = 0; 
18
 int layoutPos = 1; 
19
 mPresenter.deleteNote(new Note(), adapterPos, layoutPos); 
20
 verify(mockView).showProgress(); 
21
 verify(mockModel).deleteNote(any(Note.class), eq(adapterPos)); 
22
 verify(mockView).hideProgress(); 
23
 verify(mockView).showToast(any(Toast.class)); 
24
} 

2. Dependency Injection With Dagger 2

Dependency Injection is a great tool available to developers. If you are not familiar with dependency injection, then I strongly recommend that you read Kerry's article about the topic.

Dependency injection is a style of object configuration in which an object's fields and collaborators are set by an external entity. In other words objects are configured by an external entity. Dependency injection is an alternative to having the object configure itself. - Jakob Jenkov

In this example, dependency injection allows that the Model and Presenter are created outside the View, making the MVP layers more loosely coupled and increasing the separation of concerns.

We use Dagger 2, an awesome library from Google, to help us with dependency injection. While the setup is straightforward, dagger 2 has lots of cool options and it is a relatively complex library.

We concentrate only on the relevant parts of the library to implement MVP and won't cover the library in much detail. If you want to learn more about Dagger, read Kerry's tutorial or the documentation provided by Google.

Step 1: Setting Up Dagger 2

Start by updating the project's build.gradle file by adding a dependency.

1
dependencies { 
2
 // ...  
3
 classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' 
4
} 

Next, edit the project's build.dagger file as shown below.

1
apply plugin: 'com.neenbedankt.android-apt' 
2
 
3
dependencies { 
4
 // apt command comes from the android-apt plugin  
5
 apt 'com.google.dagger:dagger-compiler:2.0.2' 
6
 compile 'com.google.dagger:dagger:2.0.2' 
7
 provided 'org.glassfish:javax.annotation:10.0-b28' 
8
 // ...  
9
} 

Synchronize the project and wait for the operation to complete.

Step 2: Implementing MVP With Dagger 2

Let's begin by creating a @Scope for the Activity classes. Create a @annotation with the scope's name.

1
@Scope 
2
public @interface ActivityScope { 
3
} 

Next, we work on a @Module for the MainActivity. If you have multiple activities, you should provide a @Module for each Activity.

1
@Module 
2
public class MainActivityModule { 
3
 
4
 private MainActivity activity; 
5
 
6
 public MainActivityModule(MainActivity activity) { 
7
 this.activity = activity; 
8
 } 
9
 
10
 @Provides 
11
 @ActivityScope 
12
 MainActivity providesMainActivity() { 
13
 return activity; 
14
 } 
15
 
16
 @Provides 
17
 @ActivityScope 
18
 MVP_Main.ProvidedPresenterOps providedPresenterOps() { 
19
 MainPresenter presenter = new MainPresenter( activity ); 
20
 MainModel model = new MainModel( presenter ); 
21
 presenter.setModel( model ); 
22
 return presenter; 
23
 } 
24
 
25
} 

We also need a @Subcomponent to create a bridge with our application @Component, which we still need to create.

1
@ActivityScope 
2
@Subcomponent( modules = MainActivityModule.class ) 
3
public interface MainActivityComponent { 
4
 MainActivity inject(MainActivity activity); 
5
} 

We have to create a @Module and a @Component for the Application.

1
@Module 
2
public class AppModule { 
3
 
4
 private Application application; 
5
 
6
 public AppModule(Application application) { 
7
 this.application = application; 
8
 } 
9
 
10
 @Provides 
11
 @Singleton 
12
 public Application providesApplication() { 
13
 return application; 
14
 } 
15
} 
1
@Singleton 
2
@Component( modules = AppModule.class) 
3
public interface AppComponent { 
4
 Application application(); 
5
 MainActivityComponent getMainComponent(MainActivityModule module); 
6
} 

Finally, we need an Application class to initialize the dependency injection.

1
public class SampleApp extends Application { 
2
 
3
 public static SampleApp get(Context context) { 
4
 return (SampleApp) context.getApplicationContext(); 
5
 } 
6
 
7
 @Override 
8
 public void onCreate() { 
9
 super.onCreate(); 
10
 
11
 initAppComponent(); 
12
 } 
13
 
14
 private AppComponent appComponent; 
15
 
16
 private void initAppComponent(){ 
17
 appComponent = DaggerAppComponent.builder() 
18
 .appModule(new AppModule(this)) 
19
 .build(); 
20
 } 
21
 
22
 public AppComponent getAppComponent() { 
23
 return appComponent; 
24
 } 
25
} 

Don't forget to include the class name in the project's manifest.

1
<application 
2
 android:name=".SampleApp" 
3
 .... 
4
</application> 

Step 3: Injecting MVP Classes

Finally, we can @Inject our MVP classes. The changes we need to make are done in the MainActivity class. We change the way the Model and Presenter are initialized. The first step is to change the MVP_Main.ProvidedPresenterOps variable declaration. It needs to be public and we need to add a @Inject annotation.

1
@Inject 
2
public MVP_Main.ProvidedPresenterOps mPresenter; 

To set up the MainActivityComponent, add the following:

1
/**  
2
 * Setup the {@link com.tinmegali.tutsmvp_sample.di.component.MainActivityComponent}  
3
 * to instantiate and inject a {@link MainPresenter}  
4
 */ 
5
private void setupComponent(){ 
6
 Log.d(TAG, "setupComponent"); 
7
 SampleApp.get(this) 
8
 .getAppComponent() 
9
 .getMainComponent(new MainActivityModule(this)) 
10
 .inject(this); 
11
} 

All we have to do now is initialize or reinitialize the Presenter, depending on its state on StateMaintainer. Change the setupMVP() method and add the following:

1
/**  
2
 * Setup Model View Presenter pattern.  
3
 * Use a {@link StateMaintainer} to maintain the  
4
 * Presenter and Model instances between configuration changes.  
5
 */ 
6
private void setupMVP(){ 
7
 if ( mStateMaintainer.firstTimeIn() ) { 
8
 initialize(); 
9
 } else { 
10
 reinitialize(); 
11
 } 
12
} 
13
 
14
/**  
15
 * Setup the {@link MainPresenter} injection and saves in <code>mStateMaintainer</code>  
16
 */ 
17
private void initialize(){ 
18
 Log.d(TAG, "initialize"); 
19
 setupComponent(); 
20
 mStateMaintainer.put(MainPresenter.class.getSimpleName(), mPresenter); 
21
} 
22
 
23
/**  
24
 * Recover {@link MainPresenter} from <code>mStateMaintainer</code> or creates  
25
 * a new {@link MainPresenter} if the instance has been lost from <code>mStateMaintainer</code>  
26
 */ 
27
private void reinitialize() { 
28
 Log.d(TAG, "reinitialize"); 
29
 mPresenter = mStateMaintainer.get(MainPresenter.class.getSimpleName()); 
30
 mPresenter.setView(this); 
31
 if ( mPresenter == null ) 
32
 setupComponent(); 
33
} 

The MVP elements are now being configured independently form the View. The code is more organized thanks to the use of dependency injection. You could improve your code even more using dependency injection to inject other classes, such as DAO.

3. Avoiding Common Problems

I have listed a number of common problems you should avoid when using the Model View Presenter pattern.

  • Always check if the View is available before calling it. The View is tied to the application's lifecycle and could be destroyed at the time of your request.
  • Don't forget to pass a new reference from the View when it is recreated.
  • Call onDestroy() in the Presenter every time the View is destroyed. In some cases, it can be necessary to inform the Presenter about an onStop or an onPause event.
  • Consider using multiple presenters when working with complex views.
  • When using multiple presenters, the easiest way to pass information between them is by adopting some kind of event bus.
  • To maintain your View layer as passive as it can be, consider using dependency injection to create the Presenter and Model layers outside the View.

Conclusion

You reached the end of this series in which we explored the Model View Presenter pattern. You should now be able to implement the MVP pattern in your own projects, test it, and even adopt dependency injection. I hope you have enjoyed this journey as much as I did. I hope to see you soon.