Why should you care about testing? Programmers, like any human, make mistakes. We may forget about that edge case that we implemented last month, or about how some method behaves when we pass it an empty string.

There is no point in taking an app after every change and trying every possible click, tap, gesture and orientation change just to be sure that everything works. And you’ll probably forget about that triple tap in the right top corner while rotating the device so everything will crash when the user does it and something throws null pointer exception. Users do silly stuff, and we need to make sure that every class does what it’s supposed to and that every part of the app handles everything we throw at it.

That’s why we write automated tests.

1. Testing clean architecture

Clean architecture is all about maintainability and testability. Every part of the architecture has exactly one purpose. We just need to specify it and check that it actually does its job every time.

Now, let’s be realistic. What can we actually test? Everything. Genuinely, if you structure your code right, you can test everything. It’s up to you what to test. Unfortunately, there is usually no time to test everything.

Testability. That’s the first step. The second step is testing the right way. Let’s remind us about the old rule of FIRST:

Fast – Tests should be really fast. Part of the second fast. There is no point in writing tests if it takes minutes or hours to execute them. No one will check tests if that’s the case!
Isolated – Test one unit of the app at the time. Arrange everything on that unit to behave exactly how you want and then poke the testing unit. Assert that it behaves correctly.
Repeatable – Test should have the same results every time it is executed. It should not depend on some nondeterministic data.
Self-validating – Framework should know if test has passed or not. There should not be any manual checking of tests. Just check if everything is green and that’s it 🙂
Timely – Tests should be written about the same time as the code, or even before the code!

So, we made a testable app, and we know how to test. What about tests’ names?

2. Naming tests

Is it important how we name the tests, honestly? Well, it has more indirect than direct value. It reflects your attitude towards the tests, your way of thinking what to test.

Let’s meet our victim:

public final class DeleteFeedUseCase implements CompletableUseCaseWithParameter {



    @Override

    public Completable execute(final Integer feedId) {

        //implementation

    }

}

First, naive way would be to write tests like this:

@Test

public void executeWhenDatabaseReturnsTrue() throws Exception {



}



@Test

public void executeWithErrorInDatabase() throws Exception {



}

This is called implementation-style naming. It is tightly coupled with class implementation. When we change the implementation, we need to change what we expect from the class. These are usually written after the code, and the only good thing about them is that they are written quickly.

The second way is example-style naming:

@Test

public void doSomethingWithIdsSmallerThanZero() throws Exception {



}



@Test

public void ignoreWhenNullIsPassed() throws Exception {



}

Example-style tests are examples of system usage. They are nice when testing edge cases, but don’t use them for everything, they are too coupled with the implementation.

Now, let’s try to abstract our view of this class and move away from the implementation. What about this:

@Test

public void shouldDeleteExistingFeed() throws Exception {



}



@Test

public void shouldIgnoreDeletingNonExistingFeed() throws Exception {



}

We know exactly what we expect from this class. This test class can be used as class’ specification, hence the name – specification-style naming. Name does not say anything about the implementation, and from the tests’ names – specification – we can write the actual concrete class. Specification-style names are usually the best way to go, but if you think you cannot test some implementation specific edge cases, you can always throw in few example-style tests.

Theory ends here and we are ready to get our hands dirty!

3. Testing domain

Let’s see how can we test use cases. Structure of use cases in our Reedly app looks like this:

Problem is that EnableBackgroundFeedUpdatesUseCase is final and if it’s mock is needed for some other use case testing, it cannot be done. Mockito doesn’t allow mocking of final classes.
Use cases are referenced by its implementation, so let’s add another layer of interfaces:

Now we can mock EnableBackgroundFeedUpdatesUseCase interface. But in our everyday practice we concluded that this is very confusing while developing, middle layer interfaces are empty and use cases don’t actually need to have interfaces. Use cases do only one job and it says so right in the name – “enable background feed updates use case”, there is nothing to abstract!

OK, let’s try this – we don’t need to make use cases final.
We try to make everything that we can final, it makes more structured and more optimized code. We can live with use cases not being final, but there must be a better way.

We came to the solution to use mockito-inline. It makes the unmockable, mockable. With new versions of the Mockito, it is possible to enable mocking of the final classes.

Here is example of use case implementation:

public final class EnableBackgroundFeedUpdatesUseCase implements CompletableUseCase {

	private final SetShouldUpdateFeedsInBackgroundUseCase setShouldUpdateFeedsInBackgroundUseCase;

        private final FeedsUpdateScheduler feedsUpdateScheduler;


	//constructor


        @Override

        public Completable execute() {

            return setShouldUpdateFeedsInBackgroundUseCase.execute(true)

                                                          .concatWith(Completable.fromAction(feedsUpdateScheduler::scheduleBackgroundFeedUpdates));

        }

}

When testing use cases we should test that use case calls correct methods in repositories or executes other use cases. We should also test that use case returns proper callback:

private EnableBackgroundFeedUpdatesUseCase enableBackgroundFeedUpdatesUseCase;



private SetShouldUpdateFeedsInBackgroundUseCase setShouldUpdateFeedsInBackgroundUseCase;

private FeedsUpdateScheduler feedUpdateScheduler;

private TestSubscriber testSubscriber;

@Before

public void setUp() throws Exception {

    setShouldUpdateFeedsInBackgroundUseCase = Mockito.mock(SetShouldUpdateFeedsInBackgroundUseCase.class);

    feedUpdateScheduler = Mockito.mock(FeedsUpdateScheduler.class);

    testSubscriber = new TestSubscriber();


    enableBackgroundFeedUpdatesUseCase = new EnableBackgroundFeedUpdatesUseCase(setShouldUpdateFeedsInBackgroundUseCase, feedUpdateScheduler);


}



@Test

public void shouldEnableBackgroundFeedUpdates() throws Exception {

    Mockito.when(setShouldUpdateFeedsInBackgroundUseCase.execute(true)).thenReturn(Completable.complete());



    enableBackgroundFeedUpdatesUseCase.execute().subscribe(testSubscriber);



    Mockito.verify(setShouldUpdateFeedsInBackgroundUseCase, Mockito.times(1)).execute(true);

    Mockito.verifyNoMoreInteractions(setShouldUpdateFeedsInBackgroundUseCase);



    Mockito.verify(feedUpdateScheduler, Mockito.times(1)).scheduleBackgroundFeedUpdates();

    Mockito.verifyNoMoreInteractions(feedUpdateScheduler);



    testSubscriber.assertCompleted();

}

Here TestSubscriber from Rx is used, so proper callback can be tested. It can assert completion, emitted values, number of values etc.

4. Testing the data

Here is very simple repository method, it just uses one DAO method:



public final class FeedRepositoryImpl implements FeedRepository {

	private final FeedDao feedDao;


	private final Scheduler backgroundScheduler;

	//constructor


   
        @Override

        public Single feedExists(final String feedUrl) {

            return Single.defer(() -> feedDao.doesFeedExist(feedUrl))

                     .subscribeOn(backgroundScheduler);

    }

	//more methods



}

When testing repositories, you should arrange DAOs – make them return or receive some dummy data, and check that repository is handling the data in a proper way:


private FeedService feedService;

private FeedDao feedDao;

private PreferenceUtils preferenceUtils;

private Scheduler scheduler;



private FeedRepositoryImpl feedRepositoryImpl;



@Before

public void setUp() throws Exception {

    feedService = Mockito.mock(FeedService.class);

    feedDao = Mockito.mock(FeedDao.class);

    preferenceUtils = Mockito.mock(PreferenceUtils.class);

    scheduler = Schedulers.immediate();



    feedRepositoryImpl = new FeedRepositoryImpl(feedService, feedDao, preferenceUtils, scheduler);
}

@Test

public void shouldReturnInfoAboutFeedExistingIfFeedExists() throws Exception {

    Mockito.when(feedDao.doesFeedExist(DataTestData.TEST_COMPLEX_URL_STRING_1)).thenReturn(Single.just(true));



    final TestSubscriber testSubscriber = new TestSubscriber<>();

    feedRepositoryImpl.feedExists(DataTestData.TEST_COMPLEX_URL_STRING_1).subscribe(testSubscriber);



    Mockito.verify(feedDao, Mockito.times(1)).doesFeedExist(DataTestData.TEST_COMPLEX_URL_STRING_1);

    Mockito.verifyNoMoreInteractions(feedDao);

    testSubscriber.assertCompleted();



    testSubscriber.assertValue(true);

}

When testing mappers (converters), specify input to the mapper, and exact output you expect from the mapper, then assert they are equal. Do the same for services, parsers etc.

5. Testing the app module

On top of the clean architecture, we like to use MVP. Presenters are just ordinary Java objects, not connected with Android, so there is nothing special about testing them. Let’s see what can we test:

public final class ArticlesPresenterTest {



    @Test

    public void shouldFetchArticlesAndPassThemToView() throws Exception {



    }



    @Test

    public void shouldFetchFavouriteArticlesAndPassThemToView() throws Exception {



    }



    @Test

    public void shouldShowArticleDetails() throws Exception {



    }



    @Test

    public void shouldMarkArticleAsRead() throws Exception {



    }



    @Test

    public void shouldMakeArticleFavourite() throws Exception {



    }



    @Test

    public void shouldMakeArticleNotFavorite() throws Exception {



    }

}

Presenters usually have a lot of dependencies. We inject dependencies into presenters via @Inject annotation, not via the constructor. So in tests below, we need to use @Mock and @Spy annotations:

public final class ArticlesPresenter extends BasePresenter implements ArticlesContract.Presenter {



    @Inject

    GetArticlesUseCase getArticlesUseCase;



    @Inject

    FeedViewModeMapper feedViewModeMapper;



    // (...) more fields



    public ArticlesPresenter(final ArticlesContract.View view) {

        super(view);

    }



    @Override

    public void fetchArticles(final int feedId) {

        viewActionQueue.subscribeTo(getArticlesUseCase.execute(feedId)

                                                      .map(feedViewModeMapper::mapArticlesToViewModels)

                                                      .map(this::toViewAction),

                                   Throwable::printStackTrace);

    }



    // (...) more methods


}

@Mock just makes plain mock out of the class. @Spy let’s you use the existing instance with all of the methods working, but you can mock some methods and “spy” which methods are called. Mocks are injected into the presenter via @InjectMocks annotation:

@Mock

GetArticlesUseCase getArticlesUseCase;

@Mock

FeedViewModeMapper feedViewModeMapper;

@Mock

ConnectivityReceiver connectivityReceiver;


@Mock

ViewActionQueueProvider viewActionQueueProvider;



@Spy

Scheduler mainThreadScheduler = Schedulers.immediate();



@Spy

MockViewActionQueue mockViewActionHandler;



@InjectMocks

ArticlesPresenter articlesPresenter;

Then some setting up is required. View is mocked manually because it is injected via constructor and we call presenter.start() and presenter.activate() so presenter is prepared and started:

@Before

public void setUp() throws Exception {

    view = Mockito.mock(ArticlesContract.View.class);

    articlesPresenter = new ArticlesPresenter(view);

    MockitoAnnotations.initMocks(this);



    Mockito.when(connectivityReceiver.getConnectivityStatus()).thenReturn(Observable.just(true));

    Mockito.when(viewActionQueueProvider.queueFor(Mockito.any())).thenReturn(new MockViewActionQueue ());

    articlesPresenter.start();

    articlesPresenter.activate();
}

When everything is ready, we can start writing the tests. Prepare everything and make sure that the presenter calls the view if needed:

@Test

public void shouldFetchArticlesAndPassThemToView() throws Exception {

    final int feedId = AppTestData.TEST_FEED_ID;

    final List<article> articles = new ArrayList<>();
    final Article = new Article (AppTestData.TEST_ARTICLE_ID, feedId, AppTestData.TEST_STRING, AppTestData.TEST_LINK, AppTestData.TEST_LONG_DATE,
                        false, false);
    articles.add(article);

    final List<ArticleViewModel articleViewModels = new ArrayList <>();
    final ArticleViewModel articleViweModel = new ArticleViewModel(AppTestData.TEST_ARTICLE_ID, AppTestData.TEST_STRING, AppTestData.TEST_LINK, AppTestDAta.TEST_STRING,
                        false, false);
    articleViewModels.add(articleViewModel);

    Mockito.when(getArticlesUseCase.execute(feedID)).thenReturn(Single.just(articles));
    Mockito.when(feedViewModeMapper.mapArticlesToViewModels(Mockito.anyList())).thenReturn(articleViewModels);

    articlesPresenter.fetchArticles(feedId);

    Mockito.verify(getArticlesUseCase, Mockito.times(1)).execute(feedId);
    Moclito.verify(view, Mockito.times(1)).showArticles(articleViewModels);
} 

 

Conclusion

Think about testing before and during coding. That way you can write testable and decoupled code. Use your tests as class specification, and if possible write them before the code. Don’t let your ego get in the way, we all make mistakes. Therefore, we need to have a process to defend the application from ourselves!

 

This is a part of Android Architecture series. Check our other parts:

Part 4: Applying Clean Architecture on Android (Hands-on)

Part 3: Applying Clean Architecture on Android

Part 2: The Clean Architecture

Part 1: every new beginning is hard

or download them all in an E-book we released to celebrate becoming a part of a prestigious Google Developer Agency Program.

 

 

0 comments