Chapters

Hide chapters

Android Test-Driven Development by Tutorials

First Edition · Android 10 · Kotlin 1.3 · AS 3.5

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section II: Testing on a New Project

Section 2: 8 chapters
Show chapters Hide chapters

Section III: TDD on Legacy Projects

Section 3: 9 chapters
Show chapters Hide chapters

7. Introduction to Mockito
Written by Fernando Sproviero

You’ll often find yourself in situations in which you want to write a test for a method of a class that requires collaboration from another class. Unit Tests normally focus on a single class, therefore you need a way to avoid using their actual collaborators. Otherwise, you’d be doing integration testing, which you’ll see in Chapter 8, “Integration.”

In this chapter, you’ll:

  • Learn what mocking and stubbing are and when to use these techniques.
  • Write more unit tests using the test-driven development (TDD) pattern to continue testing state, and a way to also verify behavior.

Why Mockito?

If you remember from a previous chapter, whenever you create a test, you must:

  • First, configure what you’re going to test.
  • Second, execute the method that you want to test.
  • Finally, verify the result by checking the state of the object under test. This is called state verification or black-box testing. This is what you’ve done using JUnit.

However, to perform state verification, sometimes the object under test has to collaborate with another one. Because you want to focus on the first object, in the configuration phase, you want to provide a test double collaborator to your object under test. This fake collaborator is just for testing purposes and you configure it to behave as you want. For example, you could make a mock so that calling a method on it always returns the same hardcoded String. This is called stubbing a method. You’ll use Mockito for this.

There’s another type of verification called behavior verification or white-box testing. Here you want to ensure that your object under test will call specific collaborator methods. For example, you may have a repository object that retrieves the data from the network, and before returning the results, it calls a collaborator object to save them into a database. Again, you can use Mockito to keep an eye on a collaborator and verify if specific methods were called on it.

Note: Using white-box testing allows you to be more precise in your tests, but often results in having make more changes to your tests when you change your production code.

Setting up Mockito

Open the application’s build.gradle file and add the following dependency:

dependencies {
	...
	testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0'
}

Mockito-Kotlin is a wrapper library around Mockito. It provides top-level functions to allow for a more Kotlin-like approach and also solves a few issues with using the Mockito Java library in Kotlin.

Creating unit tests with Mockito

Later, in the UI, you’ll show the user a question with two options. You’ll want the user to click on one, and somehow your Game class will handle that answer, delegate to the Question class, increment the score if the answer was correct and, finally, return the next question.

Mocking and verifying

Start by adding the following test to the GameUnitTests.kt file:

  @Test
  fun whenAnswering_shouldDelegateToQuestion() {
  	// 1
    val question = mock<Question>()
    val game = Game(listOf(question))

    // 2
    game.answer(question, "OPTION")

    // 3
    verify(question, times(1)).answer(eq("OPTION"))
  }

Note: When importing mock, verify, times and eq, you should choose the options starting with com.nhaarman.mockitokotlin2.*.

In this test:

  1. You set up the test. The answer() method of Game will call answer on the Question, so you create a mock, which you can later verify against.
  2. Call the answer() method of Game, passing the Question mock as a parameter.
  3. Verify the method answer() was called on the Question mock. You used the times(1) verification mode to check that the answer() method was called exactly one time. You also used the eq argument matcher to check that the answer() method was called with a String equal to OPTION.

You can omit times(1) as it’s the default. So modify the code to the following:

verify(question).answer(eq("OPTION"))

Note: Other verification modes, like times(), are: never(), atLeast(), atMost(). Other argument matchers, like eq(), are: same(), any().

Try to compile and run the test, you’ll see an error because the Game class doesn’t have the answer() method yet.

So, open the Game class and create the answer() method:

  fun answer(question: Question, option: String) {
    // TODO
  }

Run the test. You’ll see that it doesn’t pass:

This is because Kotlin classes and methods are final by default. Mockito won’t work with final classes/methods out of the box. To fix this you have the following options:

  • Use a mock-maker-inline extension to allow Mockito mock final classes/methods.
  • Add the open keyword to classes and methods that you’ll mock.
  • Create an interface and have the class implement the interface. Then, just mock the interface (interfaces are open by default).

Using mock-maker-inline extension

Go to the project window and change to Project. Create a resources directory under app ‣ src ‣ test. Inside resources, create a directory called mockito-extensions and a file called org.mockito.plugins.MockMaker with the following content:

Now, you can go back and run the last test you created and see that it still doesn’t pass, but this time with another error:

Here it states that it was expecting an invocation to the answer() method of the Question class.

So now, fix the answer() method with the correct implementation:

  fun answer(question: Question, option: String) {
    question.answer(option)
  }

Now, run the test and see that it passes:

Stubbing methods

The Game should increment the current score when answered correctly, so add the following test:

  @Test
  fun whenAnsweringCorrectly_shouldIncrementCurrentScore() {
  	// 1
    val question = mock<Question>()
    whenever(question.answer(anyString())).thenReturn(true)

    val game = Game(listOf(question))

    // 2
    game.answer(question, "OPTION")

    // 3
    Assert.assertEquals(1, game.currentScore)
  }

In the above, you:

  1. Mocked the Question class again. Using whenever/method/thenReturn you’re stubbing the question.answer() method to always return true. Notice here you used the anyString() argument matcher as you don’t care which specific String you need to stub the call.

Note: You could choose to use a specific String matcher here, which would make the test stronger.

  1. Call the answer() method of Game.
  2. Check that the game score was incremented.

Run the test, and you will see that it fails. Add the following code to the answer() method of the Game class:

fun answer(question: Question, option: String) {
  question.answer(option)
  incrementScore()
}

Now, run the test again and you will see that it passes.

You are also going to want to check that it doesn’t increment the score when answering incorrectly. To do that, add the following test:

@Test
fun whenAnsweringIncorrectly_shouldNotIncrementCurrentScore() {
  val question = mock<Question>()
  whenever(question.answer(anyString())).thenReturn(false)
  val game = Game(listOf(question))

  game.answer(question, "OPTION")

  Assert.assertEquals(0, game.currentScore)
}

Here, instead, you are stubbing the answer() method to always return false.

Run the test and you will see that it fails. It’s a good thing you checked for that boundary condition! To fix this, replace your answer() method with the following:

fun answer(question: Question, option: String) {
  val result = question.answer(option)
  if (result) {
    incrementScore()
  }
}

This adds a check to only increment the score if the answer is correct. Now, run both tests and you will see them pass.

Refactoring

Open the Game class. Notice that this class knows about the score and a list of questions. When requesting to answer a question, the Game class delegates this to the Question class and increments the score if the answer was correct. Game could also be refactored to delegate the logic of incrementing the current score and highest score to a new class, Score.

Create a Score class in the same package as the Game class with the following content:

class Score(highestScore: Int = 0) {
  var current = 0
    private set

  var highest = highestScore
    private set

  fun increment() {
    current++
    if (current > highest) {
      highest = current
    }
  }
}

Now, update the Game class to use this new class:

class Game(private val questions: List<Question>,
           highest: Int = 0) {

  private val score = Score(highest)

  val currentScore: Int
    get() = score.current

  val highestScore: Int
    get() = score.highest

  private var questionIndex = -1

  fun incrementScore() {
    score.increment()
  }

  ...

Run the tests again and verify that everything is still working.

With that change, however, take another look at the following unit tests from GameUnitTests.kt:

@Test
fun whenIncrementingScore_shouldIncrementCurrentScore() {
  val game = Game(emptyList(), 0)

  game.incrementScore()

  Assert.assertEquals(
    "Current score should have been 1",
    1,
    game.currentScore)
}

@Test
fun whenIncrementingScore_aboveHighScore_shouldAlsoIncrementHighScore() {
  val game = Game(emptyList(), 0)

  game.incrementScore()

  Assert.assertEquals(1, game.highestScore)
}

@Test
fun whenIncrementingScore_belowHighScore_shouldNotIncrementHighScore() {
  val game = Game(emptyList(), 10)

  game.incrementScore()

  Assert.assertEquals(10, game.highestScore)
}

When calling game.incrementScore(), game.highestScore, or game.currentScore because you refactored to internally delegate to a dependent class, Score, you are now performing integration tests. You’ll see and learn more about them in Chapter 8, “Integration.”

In order to keep your tests at the unit level, remove these tests from GameUnitTests.kt and create a new file called ScoreUnitTests.kt with the following content:

class ScoreUnitTests {

  @Test
  fun whenIncrementingScore_shouldIncrementCurrentScore() {
    val score = Score()

    score.increment()

    Assert.assertEquals(
      "Current score should have been 1",
      1,
      score.current)
  }

  @Test
  fun whenIncrementingScore_aboveHighScore_shouldAlsoIncrementHighScore() {
    val score = Score()

    score.increment()

    Assert.assertEquals(1, score.highest)
  }

  @Test
  fun whenIncrementingScore_belowHighScore_shouldNotIncrementHighScore() {
    val score = Score(10)

    score.increment()

    Assert.assertEquals(10, score.highest)
  }
}

This gets your tests back to the unit level because you test the methods of the Score object without dependent classes.

Run them to check that they pass.

With that refactor, the only method that is still using the incrementScore() method in your Game class is the answer() method. Let’s simplify this. Remove the incrementScore() method and change the answer() method as follows:

fun answer(question: Question, option: String) {
  val result = question.answer(option)
  if (result) {
    score.increment()
  }
}

Now, because you removed the public scoreIncrement() method, the only way to increment the score in your Game class is by answering questions.

Next, open GameUnitTests.kt and have a look at the following tests:

@Test
fun whenAnsweringCorrectly_shouldIncrementCurrentScore() {
  val question = mock<Question>()
  whenever(question.answer(anyString())).thenReturn(true)
  val game = Game(listOf(question))

  game.answer(question, "OPTION")

  Assert.assertEquals(1, game.currentScore)
}

@Test
fun whenAnsweringIncorrectly_shouldNotIncrementCurrentScore() {
  val question = mock<Question>()
  whenever(question.answer(anyString())).thenReturn(false)
  val game = Game(listOf(question))

  game.answer(question, "OPTION")

  Assert.assertEquals(0, game.currentScore)
}

You may have guessed that now these are integration tests. This is because you are asserting game.currentScore that internally depends on a Score class from your refactor. To convert them to unit tests, you will need to change them to verify that the increment() method on the Score class was or wasn’t called. To do that, replace them with the following:

@Test
fun whenAnsweringCorrectly_shouldIncrementCurrentScore() {
  val question = mock<Question>()
  whenever(question.answer(anyString())).thenReturn(true)
  val score = mock<Score>()
  val game = Game(listOf(question), score)

  game.answer(question, "OPTION")

  verify(score).increment()
}

@Test
fun whenAnsweringIncorrectly_shouldNotIncrementCurrentScore() {
  val question = mock<Question>()
  whenever(question.answer(anyString())).thenReturn(false)
  val score = mock<Score>()
  val game = Game(listOf(question), score)

  game.answer(question, "OPTION")

  verify(score, never()).increment()
}

You’ll see that it doesn’t compile now, because you’re passing a list of questions and a score to the Game class constructor, but it doesn’t support that yet. To fix that, open your Game class and change the constructor to the following:

class Game(private val questions: List<Question>,
           val score: Score = Score(0)) {

Once that is done, remove the old score, currentScore and highestScore properties as they are not needed anymore. Your modified Game class should be the following:

class Game(private val questions: List<Question>,
           val score: Score = Score(0)) {

  private var questionIndex = -1

  fun nextQuestion(): Question? {
    if (questionIndex + 1 < questions.size) {
      questionIndex++
      return questions[questionIndex]
    }
    return null
  }

  fun answer(question: Question, option: String) {
    val result = question.answer(option)
    if (result) {
      score.increment()
    }
  }
}

Run the tests and everything should now pass. Congratulations, you have successfully refactored your tests and kept them at the unit level.

Verifying in order

To save and retrieve the high score, you’ll need to add functionality to a repository. From the Project view, create a new package common ‣ repository under app ‣ src ‣ test ‣ java ‣ com ‣ raywenderlich ‣ android ‣ cocktails. Create a new file called RepositoryUnitTests.kt and add the following code:

class RepositoryUnitTests {

  @Test
  fun saveScore_shouldSaveToSharedPreferences() {
    val api: CocktailsApi = mock()
    // 1
    val sharedPreferencesEditor: SharedPreferences.Editor =
		  mock()
    val sharedPreferences: SharedPreferences = mock()
    whenever(sharedPreferences.edit())
      .thenReturn(sharedPreferencesEditor)
    val repository = CocktailsRepositoryImpl(api,
			sharedPreferences)

    // 2
    val score = 100
    repository.saveHighScore(score)

    // 3
    inOrder(sharedPreferencesEditor) {
      // 4
      verify(sharedPreferencesEditor).putInt(any(), eq(score))
      verify(sharedPreferencesEditor).apply()
    }
  }
}

Going over each step in turn:

  1. You’re going to save the score into the CocktailsRepository using SharedPreferences, so you need to mock this dependency and instruct to return an Editor mock whenever an editor is requested.
  2. Execute the saveHighScore() method.
  3. Use inOrder to check that the subsequent verifications are executed in the exact order.
  4. Verify that the score is saved correctly.

In order for this code to compile, add a saveHighScore() method to your CocktailsRepository interface.

interface CocktailsRepository {
  ...
  fun saveHighScore(score: Int)
}

Then modify your CocktailsRepositoryImpl constructor to take in SharedPreferences as a parameter and override the saveHighScore() method:

class CocktailsRepositoryImpl(
    private val api: CocktailsApi,
    private val sharedPreferences: SharedPreferences)
  : CocktailsRepository {

  override fun saveHighScore(score: Int) {
    // TODO
  }

Run the test and see that it fails. To fix it, add the following code to the CocktailsRepositoryImpl class:

private const val HIGH_SCORE_KEY = "HIGH_SCORE_KEY"

class CocktailsRepositoryImpl(
    private val api: CocktailsApi,
    private val sharedPreferences: SharedPreferences)
  : CocktailsRepository {

  ...

  override fun saveHighScore(score: Int) {
    val editor = sharedPreferences.edit()
    editor.putInt(HIGH_SCORE_KEY, score)
    editor.apply()
  }

This is adding logic to your saveHighScore method to save it in sharedPreferences. Run the test again it will pass.

You are also going to want to have a way to read the high score from the repository. To get started, add the following test:

  @Test
  fun getScore_shouldGetFromSharedPreferences() {
    val api: CocktailsApi = mock()
    val sharedPreferences: SharedPreferences = mock()

    val repository = CocktailsRepositoryImpl(api,
			sharedPreferences)

    repository.getHighScore()

    verify(sharedPreferences).getInt(any(), any())
  }

Next, add the getHighScore() method to CocktailsRepository and CocktailsRepositoryImpl:

interface CocktailsRepository {
  ...
  fun getHighScore(): Int
}
class CocktailsRepositoryImpl(
    private val api: CocktailsApi,
    private val sharedPreferences: SharedPreferences)
  : CocktailsRepository {

  ...

  override fun getHighScore(): Int = 0

Run the test, see it fail, and then add the following code to the CocktailsRepositoryImpl class to see it pass:

  override fun getHighScore()
    = sharedPreferences.getInt(HIGH_SCORE_KEY, 0)

If you look at these two tests, you may notice that you have some code that is repeated in both of them. Let’s DRY this up by refactoring your RepositoryUnitTests so that it looks like the following:

class RepositoryUnitTests {
  private lateinit var repository: CocktailsRepository
  private lateinit var api: CocktailsApi
  private lateinit var sharedPreferences: SharedPreferences
  private lateinit var sharedPreferencesEditor: SharedPreferences.Editor

  @Before
  fun setup() {
    api = mock()
    sharedPreferences = mock()
    sharedPreferencesEditor = mock()
    whenever(sharedPreferences.edit())
      .thenReturn(sharedPreferencesEditor)

    repository = CocktailsRepositoryImpl(api, sharedPreferences)
  }

  @Test
  fun saveScore_shouldSaveToSharedPreferences() {
    val score = 100
    repository.saveHighScore(score)

    inOrder(sharedPreferencesEditor) {
      verify(sharedPreferencesEditor).putInt(any(), eq(score))
      verify(sharedPreferencesEditor).apply()
    }
  }

  @Test
  fun getScore_shouldGetFromSharedPreferences() {
    repository.getHighScore()

    verify(sharedPreferences).getInt(any(), any())
  }
}

Run the tests again to check everything is still working.

Spying

Suppose you want to only save the high score if it is higher than the previously saved high score. To do that, you want to start by adding the following test to your RepositoryUnitTests class:

  @Test
  fun saveScore_shouldNotSaveToSharedPreferencesIfLower() {
    val previouslySavedHighScore = 100
    val newHighScore = 10
    val spyRepository = spy(repository)
    doReturn(previouslySavedHighScore)
        .whenever(spyRepository)
        .getHighScore()

    spyRepository.saveHighScore(newHighScore)

    verify(sharedPreferencesEditor, never())
        .putInt(any(), eq(newHighScore))
  }

In this test you are stubbing the getHighScore() method but you also need to call the real saveHighScore() method on the same object, which is a real object, CocktailsRepositoryImpl. To do that you need a spy instead of a mock. Using a spy will let you call the methods of a real object, while also tracking every interaction, just as you would do with a mock. When setting up spies, you need to use doReturn/whenever/method to stub a method. Try running the test and you will see that it fails.

To make the test pass, modify the saveHighScore() method of the CocktailsRepositoryImpl so that it is as follows:

  override fun saveHighScore(score: Int) {
    val highScore = getHighScore()
    if (score > highScore) {
      val editor = sharedPreferences.edit()
      editor.putInt(HIGH_SCORE_KEY, score)
      editor.apply()
    }
  }

Run the test again and it will pass.

In order to make games for a user, you’ll need a factory to build a Game with questions, which will map the cocktails returned by the API. Create a CocktailsGameFactoryUnitTests.kt file under app ‣ src ‣ test ‣ java ‣ com ‣ raywenderlich ‣ android ‣ cocktails ‣ game ‣ factory. Add the following code:

class CocktailsGameFactoryUnitTests {

  private lateinit var repository: CocktailsRepository
  private lateinit var factory: CocktailsGameFactory

  @Before
  fun setup() {
    repository = mock()
    factory = CocktailsGameFactoryImpl(repository)
  }

  @Test
  fun buildGame_shouldGetCocktailsFromRepo() {
    factory.buildGame(mock())

    verify(repository).getAlcoholic(any())
  }
}

With this test, you are checking that buildGame is calling getAlcoholic from the repository.

Create the following interface and class to make it compile, under app ‣ src ‣ main ‣ java ‣ com ‣ raywenderlich ‣ android ‣ cocktails ‣ game ‣ factory:

interface CocktailsGameFactory {

  fun buildGame(callback: Callback)

  interface Callback {
    fun onSuccess(game: Game)
    fun onError()
  }
}
class CocktailsGameFactoryImpl(
    private val repository: CocktailsRepository)
  : CocktailsGameFactory {

  override fun buildGame(callback: CocktailsGameFactory.Callback) {
    // TODO
  }
}

Run the test and see that it fails. To make the test pass, add the following code to the buildGame() method:

  override fun buildGame(callback: CocktailsGameFactory.Callback) {
    repository.getAlcoholic(
        object : RepositoryCallback<List<Cocktail>, String> {
          override fun onSuccess(cocktailList: List<Cocktail>) {
            // TODO
          }

          override fun onError(e: String) {
            // TODO
          }
        })
  }

This is adding a call to the getAlcoholic method with stubbed callbacks for onSuccess and onError. Run the test again and it will pass.

Stubbing callbacks

Create a new test that verifies that the callback is called when the repository returns successfully with a list of cocktails:

  private val cocktails = listOf(
      Cocktail("1", "Drink1", "image1"),
      Cocktail("2", "Drink2", "image2"),
      Cocktail("3", "Drink3", "image3"),
      Cocktail("4", "Drink4", "image4")
  )

  @Test
  fun buildGame_shouldCallOnSuccess() {
    val callback = mock<CocktailsGameFactory.Callback>()
    setUpRepositoryWithCocktails(repository)

    factory.buildGame(callback)

    verify(callback).onSuccess(any())
  }

  private fun setUpRepositoryWithCocktails(
  	repository: CocktailsRepository) {
    doAnswer {
      // 1
      val callback: RepositoryCallback<List<Cocktail>, String>
        = it.getArgument(0)
      callback.onSuccess(cocktails)
    }.whenever(repository).getAlcoholic(any())
  }

In setUpRepositoryWithCocktails, you are using doAnswer to stub the repository.getAlcoholic() method to always return success with a list of cocktails. The doAnswer closure returns an InvocationOnMock type, with which you can spy on its arguments. You then get the first argument of the method (which is the callback), and call onSuccess() on it.

Run the test and it will fail. Now, modify the code to the onSuccess callback of the buildGame() so that buildGame() looks like the following:

  override fun buildGame(callback: CocktailsGameFactory.Callback) {
    repository.getAlcoholic(
        object : RepositoryCallback<List<Cocktail>, String> {
          override fun onSuccess(cocktailList: List<Cocktail>) {
            callback.onSuccess(Game(emptyList()))
          }

          override fun onError(e: String) {
            // TODO
          }
        })
  }

Run your test again and it will pass. Now, let’s do the same with the onError case to ensure you test the error path as well as success. First, add the following test:

  @Test
  fun buildGame_shouldCallOnError() {
    val callback = mock<CocktailsGameFactory.Callback>()
    setUpRepositoryWithError(repository)

    factory.buildGame(callback)

    verify(callback).onError()
  }

  private fun setUpRepositoryWithError(
  	repository: CocktailsRepository) {
    doAnswer {
      val callback: RepositoryCallback<List<Cocktail>, String>
        = it.getArgument(0)
      callback.onError("Error")
    }.whenever(repository).getAlcoholic(any())
  }

Here setUpRepositoryWithError() is stubbing the getAlcoholic() method to always answer with an error. Run the test and it will fail.

Now, add the following implementation to the onError callback of your buildGame function so that buildGame looks like the following:

  override fun buildGame(
		callback: CocktailsGameFactory.Callback
	) {
    repository.getAlcoholic(
        object : RepositoryCallback<List<Cocktail>, String> {
          override fun onSuccess(cocktailList: List<Cocktail>) {
            callback.onSuccess(Game(emptyList()))
          }

          override fun onError(e: String) {
            callback.onError()
          }
        })
  }

Run the test and it will pass.

The following tests are similar to what you’ve been writing, they ensure that CocktailsGameFactoryImpl builds a Game using the high score and maps the list of Cocktail objects to Question objects. They are here to give you more practice, but if you are really anxious to move on you can skip to the next section “Testing ViewModel and LiveData”.

Create the following tests that verify the factory creates a Game using the repository.getHighScore() method:

  @Test
  fun buildGame_shouldGetHighScoreFromRepo() {
    setUpRepositoryWithCocktails(repository)

    factory.buildGame(mock())

    verify(repository).getHighScore()
  }

  @Test
  fun buildGame_shouldBuildGameWithHighScore() {
    setUpRepositoryWithCocktails(repository)
    val highScore = 100
    whenever(repository.getHighScore()).thenReturn(highScore)

    factory.buildGame(object : CocktailsGameFactory.Callback {
      override fun onSuccess(game: Game)
        = Assert.assertEquals(highScore, game.score.highest)

      override fun onError() = Assert.fail()
    })
  }

As you should always do, run the tests once to make sure that they fail. To make them pass, modify your buildGame() method so that it is as follows:

  override fun buildGame(callback: CocktailsGameFactory.Callback) {
    repository.getAlcoholic(
        object : RepositoryCallback<List<Cocktail>, String> {
          override fun onSuccess(cocktailList: List<Cocktail>) {
            val score = Score(repository.getHighScore())
            val game = Game(emptyList(), score)
            callback.onSuccess(game)
          }

          override fun onError(e: String) {
            callback.onError()
          }
        })
  }

Run the tests and they will pass.

Now, create the following test that verifies the factory creates a Game mapping a list of cocktails to a list of questions:

  @Test
  fun buildGame_shouldBuildGameWithQuestions() {
    setUpRepositoryWithCocktails(repository)

    factory.buildGame(object : CocktailsGameFactory.Callback {
      override fun onSuccess(game: Game) {
        cocktails.forEach {
          assertQuestion(game.nextQuestion(),
              it.strDrink,
              it.strDrinkThumb)
        }
      }

      override fun onError() = Assert.fail()
    })
  }

  private fun assertQuestion(question: Question?,
                             correctOption: String,
                             imageUrl: String?) {
    Assert.assertNotNull(question)
    Assert.assertEquals(imageUrl, question?.imageUrl)
    Assert.assertEquals(correctOption, question?.correctOption)
    Assert.assertNotEquals(correctOption,
			question?.incorrectOption)
  }

Here, you are asserting that the image of the question that will be shown in the UI corresponds to the cocktail image, the correct option corresponds to the name of the drink, and also that the incorrect option is not the name of the drink.

If you run this, the test will not compile, so add the imageUrl property to the Question class:

class Question(val correctOption: String,
               val incorrectOption: String,
               val imageUrl: String? = null) {
...

Now run the test, which compiles but now fails. To make it pass, replace your buildGame() method with the following:

override fun buildGame(callback: CocktailsGameFactory.Callback) {
  repository.getAlcoholic(
      object : RepositoryCallback<List<Cocktail>, String> {
        override fun onSuccess(cocktailList: List<Cocktail>) {
          val questions = buildQuestions(cocktailList)
          val score = Score(repository.getHighScore())
          val game = Game(questions, score)
          callback.onSuccess(game)
        }

        override fun onError(e: String) {
          callback.onError()
        }
      })
}

private fun buildQuestions(cocktailList: List<Cocktail>)
  = cocktailList.map { cocktail ->
      val otherCocktail
          = cocktailList.shuffled().first { it != cocktail }
      Question(cocktail.strDrink,
          otherCocktail.strDrink,
          cocktail.strDrinkThumb)
    }

This adds in a buildQuestions method that creates a series of questions for the list of cocktails. This is called in your onSuccess callback in buildGame with the result passed to Game. Run the test again and it will pass.

Testing ViewModel and LiveData

To update the UI with questions, the score, and also to enable the user to interact with the question options, you’re going to use ViewModel and LiveData from Android Architecture Components. To get started, add the following dependencies in your build.gradle within the app module:

dependencies {
  ...
  implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
  testImplementation 'androidx.arch.core:core-testing:2.0.1'
}

Next, create a package called viewmodel under app ‣ src ‣ test ‣ java ‣ com ‣ raywenderlich ‣ android ‣ cocktails ‣ game. Now, create a CocktailsGameViewModelUnitTests.kt file under this viewmodel directory you just created with the following code:

class CocktailsGameViewModelUnitTests {
  @get:Rule
  val taskExecutorRule = InstantTaskExecutorRule()
}

You may have noticed @get:Rule. This is a test rule. A test rule is a tool to change the way tests run, sometimes adding additional checks or running code before and after your tests. Android Architecture Components uses a background executor that is asynchronous to do its magic. InstantTaskExecutorRule is a rule that swaps out that executor and replaces it with synchronous one. This will make sure that, when you’re using LiveData with the ViewModel, it’s all run synchronously in the tests.

Now that you have your test scaffolding, add the following to your test file:

  private lateinit var repository: CocktailsRepository
  private lateinit var factory: CocktailsGameFactory
  private lateinit var viewModel: CocktailsGameViewModel
  private lateinit var game: Game
  private lateinit var loadingObserver: Observer<Boolean>
  private lateinit var errorObserver: Observer<Boolean>
  private lateinit var scoreObserver: Observer<Score>
  private lateinit var questionObserver: Observer<Question>

  @Before
  fun setup() {
    // 1
    repository = mock()
    factory = mock()
    viewModel = CocktailsGameViewModel(repository, factory)

    // 2
    game = mock()

    // 3
    loadingObserver = mock()
    errorObserver = mock()
    scoreObserver = mock()
    questionObserver = mock()
    viewModel.getLoading().observeForever(loadingObserver)
    viewModel.getScore().observeForever(scoreObserver)
    viewModel.getQuestion().observeForever(questionObserver)
    viewModel.getError().observeForever(errorObserver)
  }

In the above:

  1. Your ViewModel will require a CocktailsRepository to save the highscore and a CocktailsGameFactory to build a game. These are dependencies, so you need to mock them.
  2. You’ll use a Game mock to stub some of its methods and verify you call methods on it.
  3. You need a few mocked observers because the Activity will observe LiveData objects exposed by the ViewModel. In the UI, you’ll show a loading view when retrieving the cocktails from the API and an error view if there’s an error retrieving the cocktails, score updates and questions. Because there’s no lifecycle here, you can use the observeForever() method.

Note: Ensure to import androidx.lifecycle.Observer.

To make the test compile, create a class under app ‣ src ‣ main ‣ java ‣ com ‣ raywenderlich ‣ android ‣ cocktails ‣ game ‣ viewmodel called CocktailsGameViewModel with the following content:

class CocktailsGameViewModel(
    private val repository: CocktailsRepository,
    private val factory: CocktailsGameFactory) : ViewModel() {

  private val loadingLiveData = MutableLiveData<Boolean>()
  private val errorLiveData = MutableLiveData<Boolean>()
  private val questionLiveData = MutableLiveData<Question>()
  private val scoreLiveData = MutableLiveData<Score>()

  fun getLoading(): LiveData<Boolean> = loadingLiveData
  fun getError(): LiveData<Boolean> = errorLiveData
  fun getQuestion(): LiveData<Question> = questionLiveData
  fun getScore(): LiveData<Score> = scoreLiveData
}

Next, add the following methods to CocktailsGameViewModelUnitTests.kt:

  private fun setUpFactoryWithSuccessGame(game: Game) {
    doAnswer {
      val callback: CocktailsGameFactory.Callback =
			  it.getArgument(0)
      callback.onSuccess(game)
    }.whenever(factory).buildGame(any())
  }

  private fun setUpFactoryWithError() {
    doAnswer {
      val callback: CocktailsGameFactory.Callback =
			  it.getArgument(0)
      callback.onError()
    }.whenever(factory).buildGame(any())
  }

You’ll use these methods to stub the buildGame() method from the CocktailsGameFactory class.

Now, add the following test:

  @Test
  fun init_shouldBuildGame() {
    viewModel.initGame()

    verify(factory).buildGame(any())
  }

Here, you’re verifying that calling initGame on the ViewModel will call buildGame from the factory.

Finally, add the corresponding implementation to your CocktailsGameViewModel to make the test compile:

  fun initGame() {
    // TODO
  }

Run the test and it will compile but won’t pass.

To make it pass, replace initGame() in CocktailsGameViewModel with the following:

  fun initGame() {
    factory.buildGame(object : CocktailsGameFactory.Callback {
      override fun onSuccess(game: Game) {
        // TODO
      }

      override fun onError() {
        // TODO
      }
    })
  }

Run the test again and it will pass.

You are going to want to show a loading view and remove the error view while building the game. To get started with that, add the following tests:

  @Test
  fun init_shouldShowLoading() {
    viewModel.initGame()

    verify(loadingObserver).onChanged(eq(true))
  }

  @Test
  fun init_shouldHideError() {
    viewModel.initGame()

    verify(errorObserver).onChanged(eq(false))
  }

In both tests, you verify that initGame publishes the correct data. When the program posts a value to a LiveData, the object calls onChanged() with the value. This is the function you are checking for.

Note: There are multiple ways you can verify the result. For example, instead of using verify(loadingObserver).onChanged(eq(true)), you could replace it with Assert.assertTrue(viewModel.getLoading().value!!) instead to achieve the same result. This alternative compares the last value of the LiveData to the expected one instead of making sure a method was called with that data.

As always, you run your new tests to ensure that they fail. To fix them, modify your initGame() method by adding the following two lines as follows:

  fun initGame() {
    loadingLiveData.value = true
    errorLiveData.value = false
    factory.buildGame(...)
  }

Run the tests again and they will pass.

You are also going to want to show the error view and stop showing the loading view when there’s a problem building the game. To get started add the following tests:

  @Test
  fun init_shouldShowError_whenFactoryReturnsError() {
    setUpFactoryWithError()

    viewModel.initGame()

    verify(errorObserver).onChanged(eq(true))
  }

  @Test
  fun init_shouldHideLoading_whenFactoryReturnsError() {
    setUpFactoryWithError()

    viewModel.initGame()

    verify(loadingObserver).onChanged(eq(false))
  }

Run the tests to ensure that they fail. To fix them, modify your onError() callback in initGame() as follows:

  override fun onError() {
    loadingLiveData.value = false
    errorLiveData.value = true
  }

Run the tests and check that they pass.

Another scenario that you will want to cover is to hide the error and loading views when the factory builds a game successfully. To get started, add these tests:

  @Test
  fun init_shouldHideError_whenFactoryReturnsSuccess() {
    setUpFactoryWithSuccessGame(game)

    viewModel.initGame()

    verify(errorObserver, times(2)).onChanged(eq(false))
  }

  @Test
  fun init_shouldHideLoading_whenFactoryReturnsSuccess() {
    setUpFactoryWithSuccessGame(game)

    viewModel.initGame()

    verify(loadingObserver).onChanged(eq(false))
  }

Here, you check the error is set to false two times. The first false value is before calling the repository to build the game, and the second one is set when the game couldn’t be built because of an error.

Run the tests to ensure that they fail. To fix these tests, modify your onSuccess() callback in initGame as follows:

  override fun onSuccess(game: Game) {
    loadingLiveData.value = false
    errorLiveData.value = false
  }

Run the tests again and they will pass.

Another requirement is to show the score when the game is built. Start by adding the following test:

  @Test
  fun init_shouldShowScore_whenFactoryReturnsSuccess() {
    val score = mock<Score>()
    whenever(game.score).thenReturn(score)
    setUpFactoryWithSuccessGame(game)

    viewModel.initGame()

    verify(scoreObserver).onChanged(eq(score))
  }

Run it to make sure it doesn’t pass. Now, modify your onSuccess() callback in initGame() as follows:

  override fun onSuccess(game: Game) {
    loadingLiveData.value = false
    errorLiveData.value = false
    scoreLiveData.value = game.score
  }

Run the test and check that it passes.

You are going to want to show the first question when the game is built. Start by adding this test:

  @Test
  fun init_shouldShowFirstQuestion_whenFactoryReturnsSuccess() {
    val question = mock<Question>()
    whenever(game.nextQuestion()).thenReturn(question)
    setUpFactoryWithSuccessGame(game)

    viewModel.initGame()

    verify(questionObserver).onChanged(eq(question))
  }

Run it to make sure that if fails. Now, modify the onSuccess() callback of initGame as follows:

  override fun onSuccess(game: Game) {
    loadingLiveData.value = false
    errorLiveData.value = false
    scoreLiveData.value = game.score
    questionLiveData.value = game.nextQuestion()
  }

Run the test and make sure that it passes.

You are going to want to show the next question when calling nextQuestion. Once again, you will start by adding a test as follows:

  @Test
  fun nextQuestion_shouldShowQuestion() {
    val question1 = mock<Question>()
    val question2 = mock<Question>()
    whenever(game.nextQuestion())
        .thenReturn(question1)
        .thenReturn(question2)
    setUpFactoryWithSuccessGame(game)
    viewModel.initGame()

    viewModel.nextQuestion()

    verify(questionObserver).onChanged(eq(question2))
  }

Here, you can see you’re stubbing the nextQuestion() method from a Game to first return question1 and then question2.

To make it compile add the nextQuestion() method method to your ViewModel as follows:

  fun nextQuestion() {
    // TODO
  }

Now run your test to make sure that it fails. To fix it, replace your nextQuestion() with the following implementation:

  fun nextQuestion() {
    game?.let {
      questionLiveData.value = it.nextQuestion()
    }
  }

Then, inside your onSuccess() in initGame() modify it as follows:

  override fun onSuccess(game: Game) {
    loadingLiveData.value = false
    errorLiveData.value = false
    scoreLiveData.value = game.score
    this@CocktailsGameViewModel.game = game
    nextQuestion()
  }

Finally, add the game variable to the class:

  private var game: Game? = null

Now, run your test and it will pass.

You have one more piece of functionality to implement. Answering a question should delegate to the answer() method of the Game, save the high score, and show the next question and score — in that order. Start off by adding this test:

@Test
fun answerQuestion_shouldDelegateToGame_saveHighScore_showQuestionAndScore() {
  val score = mock<Score>()
  val question = mock<Question>()
  whenever(game.score).thenReturn(score)
  setUpFactoryWithSuccessGame(game)
  viewModel.initGame()

  viewModel.answerQuestion(question, "VALUE")

  inOrder(game, repository, questionObserver, scoreObserver) {
    verify(game).answer(eq(question), eq("VALUE"))
    verify(repository).saveHighScore(any())
    verify(scoreObserver).onChanged(eq(score))
    verify(questionObserver).onChanged(eq(question))
  }
}

Notice, here, that you’re using inOrder() again to check the methods are called exactly in the specified order.

Add the answerQuestion() method, to make it compile:

  fun answerQuestion(question: Question, option: String) {
  }

Now, run the test to make sure that it fails. Finally, add the corresponding implementation:

  fun answerQuestion(question: Question, option: String) {
    game?.let {
      it.answer(question, option)
      repository.saveHighScore(it.score.highest)
      scoreLiveData.value = it.score
      questionLiveData.value = question
    }
  }

Run the test and check it passes.

Mockito annotations

Instead of calling the mock() and spy() methods, you can use annotations. For example, open RepositoryUnitTests.kt and modify the class definition, variable definitions and setup functions to look like the following:

@RunWith(MockitoJUnitRunner::class)
class RepositoryUnitTests {
  private lateinit var repository: CocktailsRepository
  @Mock
  private lateinit var api: CocktailsApi
  @Mock
  private lateinit var sharedPreferences: SharedPreferences
  @Mock
  private lateinit var sharedPreferencesEditor:
	  SharedPreferences.Editor

  @Before
  fun setup() {
    whenever(sharedPreferences.edit())
      .thenReturn(sharedPreferencesEditor)

    repository = CocktailsRepositoryImpl(api, sharedPreferences)
  }

Note: Be sure to import org.mockito.junit.MockitoJUnitRunner when asked.

The @RunWith(MockitoJUnitRunner::class) annotation is to instruct that you are going to write tests using Mockito. Now, you can annotate using @Mock every property that you’ll later use as mocks. Notice that in the setup() method, you removed the calls to mock for each property.

Run the tests and they will still pass.

You’ve been doing a lot of work getting logic correct in your app. To see it work in the UI, un-comment the commented implementation in CocktailsGameActivity.kt, CocktailsGameViewModelFactory.kt and CocktailsApplication.kt and run the app.

Game Screen
Game Screen

You now have a well tested working cocktail game with the help of TDD.

Challenge

Challenge: Writing another test

  • When answering incorrectly three times, it should finish the game.
  • When answering correctly three times sequentially, it should start giving double score.

Write a test for each one and add the corresponding functionality progressively to make each test pass.

Key points

  • With JUnit you can do state verification, also called black-box testing.
  • With Mockito you can perform behavior verification or white-box testing.
  • Using a mock of a class will let you stub methods simulate a particular situation in a test. It’ll also verify if one or more methods were called on that mock.
  • Using a spy is similar to using a mock, but on real instances. You’ll be able to stub a method and verify if a method was called just like a mock, but also be able to call the real methods of the instance.
  • Remember: Red, Green, Refactor

Where to go from here?

Awesome! You’ve just learned the basics of unit testing with Mockito.

Check the materials for the final and challenge versions of the code of this chapter.

Check the following references to know more about the topic:

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.