Categories
android testing

Introduction to Automated Android Testing – Part 4

In this blog post series, we are working through an example Android app called Github User Search. In the previous blog posts, we took a look at setting up your app for testing, creating API calls and writing the first basic test for the API transformations. Read part 1, part 2 and part 3 first.

This post will look at creating a presenter which communicates with the repository and conveys information to the view. This will also include writing unit tests for the presenter. The sample github repo this blog post will be working from can be found here.

Creating the Presenter

  1. In order to get started, create base interfaces called MvpView and MvpPresenter. All MVP functionality will extend these two interfaces.
    public interface MvpView {
    }
    
    public interface MvpPresenter<V extends MvpView> {
    
        void attachView(V mvpView);
    
        void detachView();
    }
  2. Create a BasePresenter. This will provide functionality to check whether a view is attached to the presenter and a way to manage RxJava subscriptions.
    public class BasePresenter<T extends MvpView> implements MvpPresenter<T> {
    
        private T view;
    
        private CompositeSubscription compositeSubscription = new CompositeSubscription();
    
        @Override
        public void attachView(T mvpView) {
            view = mvpView;
        }
    
        @Override
        public void detachView() {
            compositeSubscription.clear();
            view = null;
        }
    
        public T getView() {
            return view;
        }
    
        public void checkViewAttached() {
            if (!isViewAttached()) {
                throw new MvpViewNotAttachedException();
            }
        }
    
        private boolean isViewAttached() {
            return view != null;
        }
    
        protected void addSubscription(Subscription subscription) {
            this.compositeSubscription.add(subscription);
        }
    
        protected static class MvpViewNotAttachedException extends RuntimeException {
            public MvpViewNotAttachedException() {
                super("Please call Presenter.attachView(MvpView) before" + " requesting data to the Presenter");
            }
        }
    }
    

    As you can see above, there is a CompositeSubscription defined in the presenter. This object will hold a group of RxJava subscriptions. The detachView() method calls compositeSubscription.clear() which will unsubscribe from all subscriptions, prevent memory leaks and view crashes (code will not run when the view is destroyed as it is unsubscribed). When a subscription is created in a presenter that subclasses this object, we will call addSubscription()

  3. Create the contracts between the view and the presenter in a class called UserSearchContract. Within this class, create two interfaces one for the view and one for the presenter.
    interface UserSearchContract {
    
        interface View extends MvpView {
            void showSearchResults(List<User> githubUserList);
    
            void showError(String message);
    
            void showLoading();
    
            void hideLoading();
        }
    
        interface Presenter extends MvpPresenter<View> {
            void search(String term);
        }
    }

    In the view, there are 4 methods, showSearchResults()showLoading(), hideLoading(), showError(). In the presenter, there is a method called search().

    A presenter does not care about how a view shows the results, nor how it shows an error. Similarly, a view doesn’t care how a presenter searches as long as it uses those callbacks to notify, the implementation doesn’t matter.

    Separating the logic between the view and presenter is simple. Think about reusing the presenter for another type of UI and that will make you realise where the code should live. For instance, if you had to use Java Swing, your presenter can remain the same in that case, only your view implementation would differ. This helps you to place logic by simply asking yourself the question: Would the logic in the presenter make sense if I had a different type of UI?

  4. Now that the contracts between the view and the presenter are defined. Create/navigate to UserSearchPresenter. This is where a subscription to the UserRepository will be created, which will call the Github API.
    class UserSearchPresenter extends BasePresenter<UserSearchContract.View> implements UserSearchContract.Presenter {
        private final Scheduler mainScheduler, ioScheduler;
        private UserRepository userRepository;
    
        UserSearchPresenter(UserRepository userRepository, Scheduler ioScheduler, Scheduler mainScheduler) {
            this.userRepository = userRepository;
            this.ioScheduler = ioScheduler;
            this.mainScheduler = mainScheduler;
        }
    
    }
    

    Here the presenter extends BasePresenter  and implements the UserSearchContract.Presenter contract defined in step 3. This class will implement the search() method.

    Using constructor injection allows easy mocking of the UserRepository when trying to do unit testing. The schedulers are also injected into the constructor, as the unit tests will always use Schedulers.immediate() but in the view we will use different threads.

  5.  Now for the implementation of search() :
        @Override
        public void search(String term) {
            checkViewAttached();
            getView().showLoading();
            addSubscription(userRepository.searchUsers(term).subscribeOn(ioScheduler).observeOn(mainScheduler).subscribe(new Subscriber<List<User>>() {
                @Override
                public void onCompleted() {
    
                }
    
                @Override
                public void onError(Throwable e) {
                    getView().hideLoading();
                    getView().showError(e.getMessage()); //TODO You probably don't want this error to show to users - Might want to show a friendlier message :)
                }
    
                @Override
                public void onNext(List<User> users) {
                    getView().hideLoading();
                    getView().showSearchResults(users);
                }
            }));
        }

    First off, run checkViewAttached() this will throw an exception if the view is not attached when the method starts running. Then tell the view that it should start loading by calling showLoading(). Create a subscription to userRepository.searchUsers(). Set subscribeOn() to the ioScheduler variable as we want these network calls to happen on the IO Thread. Set observeOn()  the mainScheduler  as we want the result of this subscription to be observed on the main thread. Then add the subscription to our composite subscription by calling addSubscription.

In the onNext() method, handle the result by calling hideLoading() and showSearchResults() with the list of users returned by the API. In onError(), stop the loading and call showError() with the exception’s message.

Here is the full code for UserSearchPresenter :

package za.co.riggaroo.gus.presentation.search;


import java.util.List;

import rx.Scheduler;
import rx.Subscriber;
import za.co.riggaroo.gus.data.UserRepository;
import za.co.riggaroo.gus.data.remote.model.User;
import za.co.riggaroo.gus.presentation.base.BasePresenter;

class UserSearchPresenter extends BasePresenter<UserSearchContract.View> implements UserSearchContract.Presenter {
    private final Scheduler mainScheduler, ioScheduler;
    private UserRepository userRepository;

    UserSearchPresenter(UserRepository userRepository, Scheduler ioScheduler, Scheduler mainScheduler) {
        this.userRepository = userRepository;
        this.ioScheduler = ioScheduler;
        this.mainScheduler = mainScheduler;
    }

    @Override
    public void search(String term) {
        checkViewAttached();
        getView().showLoading();
        addSubscription(userRepository.searchUsers(term).subscribeOn(ioScheduler).observeOn(mainScheduler).subscribe(new Subscriber<List<User>>() {
            @Override
            public void onCompleted() {

            }

            @Override
            public void onError(Throwable e) {
                getView().hideLoading();
                getView().showError(e.getMessage()); //TODO You probably don't want this error to show to users - Might want to show a friendlier message :)
            }

            @Override
            public void onNext(List<User> users) {
                getView().hideLoading();
                getView().showSearchResults(users);
            }
        }));
    }
}

Writing Unit Tests for the UserSearchPresenter

Now that the presenter is defined, let’s create some unit tests for it.

  1. Select the UserSearchPresenter class name. Press “ALT + Enter” and select “Create Test”. Select the “app/src/test/java” folder as this is a unit test that requires no Android dependencies.  The resulting location of the test is the following: app/src/test/java/za/co/riggaroo/gus/presentation
  2. In the UserSearchPresenterTest, create the setup method and define the variables needed for testing.
    public class UserSearchPresenterTest {
    
        @Mock
        UserRepository userRepository;
        @Mock
        UserSearchContract.View view;
    
        UserSearchPresenter userSearchPresenter;
    
        @Before
        public void setUp() throws Exception {
            MockitoAnnotations.initMocks(this);
            userSearchPresenter = new UserSearchPresenter(userRepository, Schedulers.immediate(), Schedulers.immediate());
            userSearchPresenter.attachView(view);
        }
    }

    By creating a mock instance of UserRepository and the UserSearchContract.View we will ensure that we are only testing the UserSearchPresenter. In the setUp() method, we call MockitoAnnotations.initMocks() . Then create the search presenter with the mocked objects and immediate schedulers. Call attachView() with the mock view object as the presenter works only once a view is attached.

  3. The first test will test that a valid search term has the correct callbacks:
        private static final String USER_LOGIN_RIGGAROO = "riggaroo";
        private static final String USER_LOGIN_2_REBECCA = "rebecca";
       
        @Test
        public void search_ValidSearchTerm_ReturnsResults() {
            UsersList userList = getDummyUserList();
            when(userRepository.searchUsers(anyString())).thenReturn(Observable.<List<User>>just(userList.getItems()));
    
            userSearchPresenter.search("riggaroo");
    
            verify(view).showLoading();
            verify(view).hideLoading();
            verify(view).showSearchResults(userList.getItems());
            verify(view, never()).showError(anyString());
        }
    
        UsersList getDummyUserList() {
            List<User> githubUsers = new ArrayList<>();
            githubUsers.add(user1FullDetails());
            githubUsers.add(user2FullDetails());
            return new UsersList(githubUsers);
        }
    
        User user1FullDetails() {
            return new User(USER_LOGIN_RIGGAROO, "Rigs Franks", "avatar_url", "Bio1");
        }
    
        User user2FullDetails() {
            return new User(USER_LOGIN_2_REBECCA, "Rebecca Franks", "avatar_url2", "Bio2");
        }

    This test asserts that: Given the user repository returns a set of users, when calling search() on the presenter, then the view methods showLoading() and showSearchResults() are called. This test also asserts that the showError() method is never called.

  4. The next test is one that tests the negative scenario if the UserRepository throws an error:
        @Test
        public void search_UserRepositoryError_ErrorMsg() {
            String errorMsg = "No internet";
            when(userRepository.searchUsers(anyString())).thenReturn(Observable.error(new IOException(errorMsg)));
    
            userSearchPresenter.search("bookdash");
    
            verify(view).showLoading();
            verify(view).hideLoading();
            verify(view, never()).showSearchResults(anyList());
            verify(view).showError(errorMsg);
        }

    This test is testing the following: Given the userRepository  returns an exception, when calling search()  then showError() should be called.

  5. The last test we will add will assert that if the view is not attached, an exception will be thrown.
        @Test(expected = BasePresenter.MvpViewNotAttachedException.class)
        public void search_NotAttached_ThrowsMvpException() {
            userSearchPresenter.detachView();
    
            userSearchPresenter.search("test");
    
            verify(view, never()).showLoading();
            verify(view, never()).showSearchResults(anyList());
        }
  6. Let’s run the tests and see how much test coverage we have. Right click on the test name and click “Run tests with coverage”Unit Test Coverage from the Presenter Test

We have 100% Coverage of the UserSearchPresenter! Yay!

The next blog post will cover creating the view and writing tests for the view. Make sure you subscribe so you don’t miss the next post in this series!

Subscribe to Blog via Email

Enter your email address to subscribe to this blog and receive notifications of new posts by email.


If you like my work, please consider buying me a virtual cupcake to keep the blog posts coming!

[buy_cupcake]

References:

This post series was based on a couple of implementations of MVP that can be found around the web. For reference, these are some of the articles used to get to this solution:

7 replies on “Introduction to Automated Android Testing – Part 4”

Great article!

Question: in UserSearchPresenter, isn’t it possible for detachView to be called after search() but before onNext()? Then onNext() will NPE, right?

Ah thanks! I have swapped the compositeSubscription.clear() to happen before setting the view to null. So it shouldn’t throw a NPE.
By calling compositeSubscription.clear() in detachView() the onNext() won’t be called as it has been unsubscribed. 🙂

Hi Antonio,
Thanks for the comment.
TBH, I think it is a matter of preference and what works for your project. This is not meant to be the ultimate way to write a android app, there are obviously many ways to accomplish it.
I have found that using interfaces makes the code cleaner and it is also easier to see what the various classes should implement.
I find unit tests useful for the more complicated logic that might be difficult to test using espresso tests as some stuff wont have any UI effect. JVM Unit tests are also a lot quicker than Espresso tests. I have worked on projects where there is no interfaces and I think for newer developers it is easy to go off track if you don’t have that thinking in place.
Like I said, it is all a matter of preference though. The next post of mine will do espresso testing which will serve as an integration test.
My aim for the blog post series is to get people writing tests and understanding the different types and how I do it.

Hello Rebecca, thanks for sharing this amazing project! I just miss in your project a key part which is handling change of states in the phone, like rotation. What do you use or recommend for it? Thanks and keep it up!

Hi Theo, Thanks for the comment. While I have heard of the Mosby framework, I am only looking into the implementation now . While I can see there are similarities, this series was based on a couple of implementations of MVP which were referenced in part 1. Mainly the ribot labs guideline was used ( https://labs.ribot.co.uk/android-application-architecture-8b6e34acda65#.wmla2wg5k), this might not have been clear – I will include a reference to that link again in this article.

I think implementing a simple version of MVP yourself is valuable for developers to understand before choosing a framework (like Mosby) that takes care of the intricacies for you. The purpose of this series is to demonstrate how to test your code using MVP, I see the Mosby framework has more concrete solutions for things such as rotation and view state, which this implementation lacks.

Comments are closed.