Test-driven development (TDD) is the de-facto standard in development industry. It's being said "If it is not tested, it's broken :- Bruce Eckel"
In the previous article MVP Architecture, we have covered the basics to incorporate MVP architecture and now this article will cover the testing of MVP architecture.
This section is divided into two parts to test the MVP UI using an instrumentation test and business logic using a junit test. This section is focused on testing the project specific logic rather than testing framework working.
To test the business logic of presenter, we required to mock the UI and repository components so the primary focus will be on testing the presenter and its interaction with other components like view and repository instance. To apply mocking, we will use the mockito framework.
The unit test classes created under module-name/src/test/java/
package run on local JVM. These test are quick in nature and have no Android API interaction.
To create either a local unit test or an instrumented test, you can create a new test for a specific class or method by following these steps:
ArgumentCaptor
view
and movieRepo
using @Mock
annotation to test the complete functionality of presenter.@Before
are used to setup the testing environment and dependencies, in this case, setup the mocking framework and initialize the presenter using mocked instances.argumentCaptor
instance to capture the callback interface reference and to provide response to mocked view instance.view
instance.MoviePresenterTest.java
1public class MoviePresenterTest {
2
3 private static final Random RANDOM = new Random();
4
5 @Mock // 1
6 private MoviesListContract.View view;
7
8 @Mock // 1
9 private MovieRepo movieRepo;
10
11 private MoviePresenter presenter;
12
13 @Captor // 3
14 private ArgumentCaptor<MoviesListContract.OnResponseCallback> argumentCaptor;
15
16 @Before // 2
17 public void setUp(){
18 // A convenient way to inject mocks by using the @Mock annotation in Mockito.
19 // For mock injections , initMocks method needs to be called.
20 MockitoAnnotations.initMocks(this);
21
22 // get the presenter reference and bind with view for testing
23 presenter = new MoviePresenter(view,movieRepo);
24
25
26 }
27
28 @Test
29 public void loadMoviewList() throws Exception {
30 presenter.loadMoviewList();
31 verify(movieRepo,times(1)).getMovieList(argumentCaptor.capture());
32 argumentCaptor.getValue().onResponse(getList());
33 verify(view,times(1)).hideProgress();
34 ArgumentCaptor<List> entityArgumentCaptor = ArgumentCaptor.forClass(List.class);
35 verify(view).showMovieList(entityArgumentCaptor.capture());
36
37 assertTrue(entityArgumentCaptor.getValue().size() == 10);
38 }
39
40 @Test
41 public void OnError() throws Exception {
42 presenter.loadMoviewList();
43 verify(movieRepo,times(1)).getMovieList(argumentCaptor.capture());
44 argumentCaptor.getValue().onError("Error");
45 ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);
46 verify(view,times(1)).showLoadingError(argumentCaptor.capture()); // 3
47 verify(view).showLoadingError(argumentCaptor.getValue()); // 4
48 }
49
50 private List<Movie> getList() {
51 ArrayList<Movie> movies = new ArrayList<>();
52 try {
53 movies.add(new Movie(RANDOM.nextInt(Integer.MAX_VALUE), "IT", Utility.convertStringToDate("2017-10-8"), 7.6, 127, Movie.Type.HORROR));
54 movies.add(new Movie(RANDOM.nextInt(Integer.MAX_VALUE), "Jumanji 2", Utility.convertStringToDate("2018-12-20"), 8.3, 111, Movie.Type.ACTION));
55 movies.add(new Movie(RANDOM.nextInt(Integer.MAX_VALUE), "The Dark Knight", Utility.convertStringToDate("2008-07-08"), 9.0, 152, Movie.Type.ACTION));
56 movies.add(new Movie(RANDOM.nextInt(Integer.MAX_VALUE), "Inception", Utility.convertStringToDate("2010-07-16"), 8.8, 148, Movie.Type.ACTION));
57 movies.add(new Movie(RANDOM.nextInt(Integer.MAX_VALUE), "The Green Mile", Utility.convertStringToDate("1999-12-10"), 8.5, 189, Movie.Type.DRAMA));
58 movies.add(new Movie(RANDOM.nextInt(Integer.MAX_VALUE), "Transcendence", Utility.convertStringToDate("2014-04-18"), 6.3, 120, Movie.Type.FICTION));
59 movies.add(new Movie(RANDOM.nextInt(Integer.MAX_VALUE), "Saving Private Ryan", Utility.convertStringToDate("1998-07-24"), 8.6, 169, Movie.Type.DRAMA));
60 movies.add(new Movie(RANDOM.nextInt(Integer.MAX_VALUE), "Whiplash", Utility.convertStringToDate("2015-02-20"), 8.5, 117, Movie.Type.DRAMA));
61 movies.add(new Movie(RANDOM.nextInt(Integer.MAX_VALUE), "The Raid", Utility.convertStringToDate("2011-04-13"), 7.6, 111, Movie.Type.ACTION));
62 movies.add(new Movie(RANDOM.nextInt(Integer.MAX_VALUE), "Burnt", Utility.convertStringToDate("2015-10-30"), 6.6, 111, Movie.Type.DRAMA));
63 } catch (ParseException e) {
64 e.printStackTrace();
65 }
66 return movies;
67 }
68}
To perform automated testing on a UI, we will use Espresso framework to perform the instrumentation test. Espresso is not aware of any asynchronous operation or background thread. In order to make Espresso aware of your app's long-running operations, we will use CountingIdlingResource.
MoviePresent
must increment the idling resource to make espresso aware of the idle time as shown below
1 @Override
2 public void loadMoviewList() {
3 view.showProgress();
4 mclient.getMovieList(callback);
5
6 // required for espresso UI testing
7 EspressoTestingIdlingResource.increment();
8 }
and decrement it when the response is received.
The idling resource is accessed in a static manner using a utility class EspressoTestingIdlingResource
1public class EspressoTestingIdlingResource {
2 private static final String RESOURCE = "GLOBAL";
3
4 private static CountingIdlingResource mCountingIdlingResource =
5 new CountingIdlingResource(RESOURCE);
6
7 public static void increment() {
8 mCountingIdlingResource.increment();
9 }
10
11 public static void decrement() {
12 mCountingIdlingResource.decrement();
13 }
14
15 public static IdlingResource getIdlingResource() {
16 return mCountingIdlingResource;
17 }
18
19}
Check the presenter code for complete implementation in repository
@Before
annotated method to make Espresso aware of the asynchronous delay.MovieListTest.java
1@RunWith(AndroidJUnit4.class)
2public class MovieListTest {
3
4
5 @Rule
6 public ActivityTestRule<MoviesListActivity> activityTestRule =
7 new ActivityTestRule<>(MoviesListActivity.class);
8
9 /**
10 * Register IdlingResource resource to tell Espresso when your app is in an
11 * idle state. This helps Espresso to synchronize test actions.
12 */
13
14 @Before // 1
15 public void registerIdlingResource() {
16 IdlingRegistry.getInstance().register(EspressoTestingIdlingResource.getIdlingResource());
17 }
18
19 /**
20 * Unregister your Idling Resource so it can be garbage collected and does not leak any memory.
21 */
22 @After
23 public void unregisterIdlingResource() {
24 IdlingRegistry.getInstance().unregister(EspressoTestingIdlingResource.getIdlingResource());
25 }
26
27 @Test // 2
28 public void checkStaticView() {
29 // verify default empty text message
30 onView(withId(R.id.swipe_msg_tv)).check(matches(isDisplayed()));
31 // |--------------------------|
32 //|----------------------------| find a view | using swipe_msg_tv id
33 //check visibility of view on screen <-------|
34 }
35
36 @Test
37 public void checkRecyclerViewVisibility() {
38 // 3. perform swipe
39 onView(withId(R.id.swipe_msg_tv)).check(matches(isDisplayed()));
40 onView(withId(R.id.swipe_container)).perform(swipeDown());
41
42 // verify swipe is displayed
43 onView(withId(R.id.swipe_msg_tv)).check(matches(not(isDisplayed())));
44
45 // 4 verify recycler view is displayed
46 onView(withId(R.id.movies_recycler_list)).check(matches(isDisplayed()));
47 // 5 perform click on item at 0th position
48 onView(withId(R.id.movies_recycler_list))
49 .perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
50
51 // 6 verify the toast text
52 MoviesListActivity activity = activityTestRule.getActivity();
53 onView(withText("Title : 'IT' Rating : '7.6'")).
54 inRoot(withDecorView(not(is(activity.getWindow().getDecorView())))).
55 check(matches(isDisplayed()));
56 }
57}
Robolectric : Robolectric is a testing framework which let you test the android framework components(activity, service etc) with any emulator.It's gives you control over the life cycle of components to test as well.
Continuous integration with circle ci and Travis: It is a software development practice in shared repository environment which allow developers/teams to focus on development with automated build system.
Google samples for android testing: Collection of testing examples for android by google.
I hope that testing will now help you to be a stellar programmer, you can share your love by giving this guide a thumbs up.