Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Android Clean Code:  Part 4 - Router

DZone's Guide to

Android Clean Code:  Part 4 - Router

Learn more about the Clean Code Android mobile design pattern and unit testing your Android app piece by piece for code quality.

Free Resource

Download this comprehensive Mobile Testing Reference Guide to help prioritize which mobile devices and OSs to test against, brought to you in partnership with Sauce Labs.

Having understood how to test the Android Activity, Interactor and Presenter in Part 1, Part 2, and Part 3 of this series, let’s look at the Router and its testing.

Should we handle the next Activity determination logic in Activity class?

No, we should move it separate class - Router, respecting single responsibility principle (SRP).

What Is the Work of Router?

Image title

A) Determine the next screen.

B) Pass the data needed for the next screen.

Typically, this logic is handled by the Activity class. Is it possible to unit test such logic in the Activity class?

No, you have too many objects to mock — we are moving them to Router so that we can test the logic, piece by piece.

In this example, if the trip date is in the past, the Router should start the next Activity with the intent of PastTripActivity ; if the date is current or in the future, the Router should start the next Activity with the intent of BoardingActivity.

Let’s look at Router:

Interface HomeRouterInput{
    Intent determineNextScreen(int position);
    void passDataToNextScene(int position, Intent intent);
}
public class HomeRouter implements HomeRouterInput, AdapterView.OnItemClickListener {
    public static String TAG = HomeRouter.class.getSimpleName();
    public WeakReference<HomeActivity> activity;
    private Calendar currentTime;

    @NonNull
    @Override
    public Intent determineNextScreen(int position) {
        //Based on the position or someother data decide what is the next scene
        FlightModel flight = activity.get().listOfVMFlights.get(position);
        Calendar startingTime = CalendarUtil.getCalendar(flight.startingTime);
        if(isFutureFlight(startingTime)) {
            return new Intent(activity.get(), BoardingActivity.class);
        } else {
            return new Intent(activity.get(), PastTripActivity.class);
        }
    }

    @Override
    public void passDataToNextScene(int position, Intent intent) {
        //Based on the position or someother data decide the data for the next scene
        FlightModel flight = activity.get().listOfVMFlights.get(position);
        intent.putExtra("flight",flight);
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        Intent intent = determineNextScreen(position);
        passDataToNextScene(position, intent);
        activity.get().startActivity(intent);
    }

    private boolean isFutureFlight(Calendar startingTime){
        long startTimeInMills = startingTime.getTimeInMillis();
        long currentTimeInMills = getCurrentTime().getTimeInMillis();
        return startTimeInMills >= currentTimeInMills;
    }

    public Calendar getCurrentTime() {
        if(currentTime == null) return Calendar.getInstance();
        return currentTime;
    }
    public void setCurrentTime(Calendar currentTime) { this.currentTime = currentTime;}
}

HomeRouter implements the interfaces HomeRouterInput and AdapterView.OnItemClickListener.

The class member activity of the type Activity is a WeakReference so that we don’t create circular references. These members were wired by Configurator, which will be explained in a future post.

android-clean-code-generator generates these classes, which will be explained in a future post.

If you look at the code again, you will be able to understand the logic; it is straightforward.

Unit Testing

Let’s look at the unit testing the code, we will start with the test to verify whether if the date is in Future, the method determineNextActivityshould return the intent of BoardingActivity.

Should we use the Activity class to test the router?

No, we should use the mock of Activity. We are using Robolectric.setUpActivity to create the mock.

@Test
    public void homeRouter_determineNextScreen_when_futureTripIs_Input() {
        //Given
        HomeRouter homeRouter = new HomeRouter();
        ArrayList<FlightViewModel> flightList = new ArrayList<>();
        FlightViewModel flight1 = new FlightViewModel();
        flight1.flightName = "9Z 231";
        flight1.startingTime = "2017/12/31";
        flight1.departureCity = "BLR";
        flight1.arrivalCity = "CJB";
        flight1.departureTime = "18:10";
        flight1.arrivalTime = "19:00";
        flightList.add(flight1);

        FlightViewModel flight2 = new FlightViewModel();
        flight2.flightName = "9Z 222";
        flight2.startingTime = "2016/12/31";
        flight2.departureCity = "BLR";
        flight2.arrivalCity = "CJB";
        flight2.departureTime = "18:10";
        flight2.arrivalTime = "19:00";
        flightList.add(flight2);

        //Create Mock
        HomeActivity homeActivityMock = Robolectric.setupActivity(HomeActivity.class);
        homeActivityMock.listOfVMFlights = flightList;
        homeActivityMock.router = homeRouter;
        homeRouter.activity = new WeakReference<HomeActivity>(homeActivityMock);

        Calendar currentTime = Calendar.getInstance();
        currentTime.set(2017,5,30,0,0,0);
        homeRouter.setCurrentTime(currentTime);

        //When - Futrure Trip is Input
        Intent intent = homeRouter.determineNextScreen(0);

        //Then
        String targetActivityName = intent.getComponent().getClassName();
        Assert.assertEquals("When the future travel date is passed to HomeRouter"
                +" Then next Intent should be BoardingActivity",
                            targetActivityName, BoardingActivity.class.getName());
    }

Note that the unit test execution can happen at any date and we are setting the travel date as 2017/12/31. As we determine our logic based on dates, it is important for unit testing; we should the current date fixed, so that the test execution is consistent irrespective of the date of unit test execution (see Repeatable in FIRST principle of unit testing). We have a method called setCurrentDate created just to facilitate the testing.

Ohh, wait a minute, are you creating a method in production code, just to facilitate the testing?

Yes, having a method that helps unit testing is always better that than no unit testing and living with the non-deterministic behavior of your app.

Like the above example, we can test the each and every public method of the Router. By this time, you may have realized how powerful is Android Clean Code design pattern — it helps you unit test the app piece by piece, without hardwiring dependencies. Check out the Router test class here.

I suggest you clone the example project, if you have not done so already, and before we close this exercise, run the test cases with code coverage (Android Studio → Menu →Run →Run with coverage). Check the code coverage of the Router class.

What’s next? We’ll talk about Configurator and code scaffolding in the next post.

Analysts agree that a mix of emulators/simulators and real devices are necessary to optimize your mobile app testing - learn more in this white paper, brought to you in partnership with Sauce Labs.

Topics:
clean code ,unit testing ,tdd ,mobile ,android ,mobile testing

Published at DZone with permission of Mohanraj Karatadipalayam. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}