Chapters

Hide chapters

Advanced Android App Architecture

First Edition · Android 9 · Kotlin 1.3 · Android Studio 3.2

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

16. Testing VIPER
Written by Aldo Olivares

In the last chapter, you refactored WeWatch to use the View-Interactor-Presenter-Entity-Router (better know as VIPER) architecture pattern. In this chapter, you’ll learn how to test your VIPER architecture by creating unit tests and mocking classes using Mockito.

Getting started

Start by opening the starter project and selecting the app build.gradle from the project.

Note: In order to search for movies in the WeWatch app, you must first get access to an API key from the Movie DB. To get your API own key, sign up for an account at www.themoviedb.org. Then, navigate to your account settings on the website, view your settings for the API, and register for a developer API key. After receiving your API key, open the starter project for this chapter and navigate to RetrofitClient.kt. There, you can replace the existing value for API_KEY with your own.

After the starter project finishes loading and building, run the app on a device or emulator and try adding a new movie by tapping the + float action button.

Type in a title and press the icon to the right to search for a movie.

Nothing shows up, indicating there’s some kind of problem.

At this point, it’s unclear whether there’s an issue with the search function or the TMDB API. Try adding a movie manually using Add Movie. Notice the main screen is still empty.

Something is clearly wrong — and thanks to unit testing, you’ll be able to find out what it is by creating unit tests for each of your Presenters.

Testing your Main presenter

For this project, you’ll use Mockito to mock the necessary dependencies for your app, and you’ll use JUnit to perform the tests.

Open App/build.gradle and add the Mockito and JUnit dependencies to the dependencies block:

testImplementation 'junit:junit:4.12'
testImplementation 'com.nhaarman:mockito-kotlin-kt1.1:1.5.0'

Sync the project Gradle files and run the app.

Because the problem with the app might be that the View is not calling the right method from the Presenter, the first thing you need to do is test MainPresenter, which is in charge of commanding everything in MainActivity.

Open MainPresenter.kt and select the class name. Press Alt-Enter, or Option-Return on a Mac, and select Create test.

Then, click OK.

In the next window, select test and click OK.

Android Studio creates a new MainPresenterTest empty test class for you to test MainPresenter.

To test MainPresenter, you need four things: a MainPresenter instance, a Router, a View and an Interactor.

Add the following code to MainPresenterTest:

@RunWith(MockitoJUnitRunner::class)
class MainPresenterTest {
    private lateinit var presenter: MainPresenter
}

You’ll use the presenter property to create a MainPresenter instance to test.

Add the following properties immediately below presenter:

@Mock
private lateinit var view: MainContract.View
@Mock
private lateinit var interactor: MainContract.Interactor

You need a View and an Interactor to test MainPresenter. You’ll use Mockito to mock your View and your Interactor. The @Mock annotation is an abbreviated way to inform Mockito that these dependencies are mocked and will be initialized later.

Add the following method:

//1
@Before
fun setup() {
  //2
  val cicerone = Cicerone.create()
  //4
  presenter = MainPresenter(view, interactor, cicerone.router)
}

Here’s what’s happening:

  1. @Before is an annotation that informs Mockito that this function should be executed before any tests. When testing a class with Mockito, it’s common to have one setup() function that initializes all of the mocks and classes you need for your tests.
  2. Here, you create a Cicerone instance to get a Router for MainPresenter.
  3. You create a MainPresenter instance by passing the mocks you just created.

Typically, you should have at least one unit test for each function in your class. Since MainPresenter has six methods, you need at least six tests. However, to keep things short and sweet, you’ll only create tests for a few of these methods.

Getting back to your mission, you need to investigate why the list of movies isn’t showing up in your View. It’s not obvious where the bug might be, so you’ll start by testing the View’s displayMovieList method. Add the following test:

@Test
fun displayMoviesOnQuerySuccess() {
  //1
  val movieList = listOf(Movie())
  //2
  presenter.onQuerySuccess(movieList)
  //3
  verify(view).displayMovieList(movieList)
}

Here’s what’s happening:

  1. This creates a new list containing a single movie.
  2. Here, you call the Presenter’s onQuerySuccess().
  3. This verifies that your View’s displayMovieList() is called with the appropriate list when onQuerySuccess() is invoked.

Now that your test is ready, click the green arrow to the left.

Look at the console, and you’ll see the test passes:

Because this test passes, you now know that displayMovieList() is properly getting called with a movie list when onQuerySuccess() is invoked in MainPresenter.

Next you’ll check if the View’s loadMovieList might be the problem, so add the following test:

@Test
fun loadMovies() {
  presenter.onViewCreated()
  verify(interactor).loadMovieList()
}

This test verifies that the Presenter is calling the Interactor’s loadMovieList().

To run all of the tests at the same time, click the green arrow next to the test class name.

Interesting. It looks like the problem is someplace else in the code. Perhaps it’s in the AddMoviePresenter?

Testing the AddMovie presenter

In this section, you’ll create a test class for AddMoviePresenter.

Open AddPresenter.kt and add a test for this class. Remember to add the test to the test directory when prompted.

Next, add the following properties and method to AddPresenterTest:

private lateinit var presenter: AddPresenter
@Mock
private lateinit var view: AddContract.View
@Mock
private lateinit var interactor: AddContract.Interactor

@Before
fun setup() {

  val cicerone = Cicerone.create()
  val router = cicerone.router

  presenter = AddPresenter(view, interactor, router)
}

Similar to MainPresenterTest, you need the AddPresenter instance and the mocks for the View and Interactor.

Now, add the following test:

@Test
fun addMovieWithTitle() {
  val movie = Movie(title = "Test Movie", releaseDate = "1991")
  presenter.addMovies(movie.title!!, movie.releaseDate!!)
  verify(interactor).addMovie(movie)
}

Here, you create a new Movie instance and invoke addMovies() of AddPresenter to tell the Interactor that you need to save the movie.

Execute the test.

That’s right, the test fails! Look at the console, and you’ll see the following messages:

Wanted but not invoked:
interactor.addMovie

There were zero interactions with this mock.

The first message tells you that addMovie() was never invoked and the second message is warning you that there were zero interactions with the Interactor, which means you never used any of the Interactor’s methods.

Excellent, now you know what’s causing the problem — addMovies() is not getting called in AddPresenter — and you can fit it.

Open AddPresenter.kt and add the following lines to addMovies() immediately below the call to router.navigateTo():

val movie = Movie(title = title, releaseDate = year)
interactor?.addMovie(movie)

Run the test again.

The test passes!

Build and run the app again, and try to manually add a movie. Don’t forget to give it a title. When you’re done, tap Add Movie.

So, now your new movie is visible on the screen.

There’s only one step remaining: To test SearchMoviePresenter.

Testing SearchMovie Presenter

It’s time to create a test class for SearchPresenter following the usual steps.

After adding the usual boilerplate to SearchPresenterTest (remembering to add an instance of SearchPresenter), add a test to verify the View hides the progress bar and displays the movie list received from the Presenter:

@Test
fun displayMovieListAndHideLoadingOnQuerySuccess() {
  val movieList = listOf(Movie(title = "Best Movie"))
  presenter.onQuerySuccess(movieList)
  verify(view).hideLoading()
  verify(view).displayMovieList(movieList)
}

Click the green arrow to the left of your method to run your test.

Notice the test fails. Inspect the logs, and you’ll see something interesting:

Wanted but not invoked:
view.displayMovieList

However, there was exactly 1 interaction with this mock:
view.hideLoading();

The logs tell you that AddPresenter interacted with the View but only with hideLoading(), not with displayMovieList().

Open SearchPresenter.kt and look at onQuerySuccess():

override fun onQuerySuccess(data: List<Movie>) {
  view?.hideLoading()
}

Yes, there’s something wrong here. This method is telling the View to hide the progress bar. The trouble is, it never actually invokes any method to display the movie list passed as a parameter.

If you inspect the View’s methods, you’ll see that there’s only one method that accepts a movie list as a parameter: displayMovieList().

Inside onQuerySuccess(), add the following immediately below hideLoading():

view?.displayMovieList(data)

Run your test again to see if this solves the problem.

Excellent, the test succeeds! But now you need to make sure your app works.

Build and run the app, and try searching for a movie of your choice.

The movies from the TMDB API are properly displayed:

Select any movie and click OK to save it.

The movie is also displayed in your main screen. Well done!

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.