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

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

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'
}

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"))
  }
verify(question).answer(eq("OPTION"))
  fun answer(question: Question, option: String) {
    // TODO
  }

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:

Pini is chivoq mmij uw wud isgayvors iz imbaqiqaoc pi rse opqcop() qespol ed hla Xuecreok shegw.

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

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)
  }
fun answer(question: Question, option: String) {
  question.answer(option)
  incrementScore()
}
@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)
}
fun answer(question: Question, option: String) {
  val result = question.answer(option)
  if (result) {
    incrementScore()
  }
}

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.

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

  var highest = highestScore
    private set

  fun increment() {
    current++
    if (current > highest) {
      highest = current
    }
  }
}
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()
  }

  ...
@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)
}
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)
  }
}
fun answer(question: Question, option: String) {
  val result = question.answer(option)
  if (result) {
    score.increment()
  }
}
@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)
}
@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()
}
class Game(private val questions: List<Question>,
           val score: Score = Score(0)) {
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()
    }
  }
}

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()
    }
  }
}
interface CocktailsRepository {
  ...
  fun saveHighScore(score: Int)
}
class CocktailsRepositoryImpl(
    private val api: CocktailsApi,
    private val sharedPreferences: SharedPreferences)
  : CocktailsRepository {

  override fun saveHighScore(score: Int) {
    // TODO
  }
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()
  }
  @Test
  fun getScore_shouldGetFromSharedPreferences() {
    val api: CocktailsApi = mock()
    val sharedPreferences: SharedPreferences = mock()

    val repository = CocktailsRepositoryImpl(api,
			sharedPreferences)

    repository.getHighScore()

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

  ...

  override fun getHighScore(): Int = 0
  override fun getHighScore()
    = sharedPreferences.getInt(HIGH_SCORE_KEY, 0)
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())
  }
}

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))
  }
  override fun saveHighScore(score: Int) {
    val highScore = getHighScore()
    if (score > highScore) {
      val editor = sharedPreferences.edit()
      editor.putInt(HIGH_SCORE_KEY, score)
      editor.apply()
    }
  }
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())
  }
}
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
  }
}
  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
          }
        })
  }

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())
  }
  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
          }
        })
  }
  @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())
  }
  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()
          }
        })
  }
  @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()
    })
  }
  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()
          }
        })
  }
  @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)
  }
class Question(val correctOption: String,
               val incorrectOption: String,
               val imageUrl: String? = null) {
...
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)
    }

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'
}
class CocktailsGameViewModelUnitTests {
  @get:Rule
  val taskExecutorRule = InstantTaskExecutorRule()
}
  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)
  }
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
}
  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())
  }
  @Test
  fun init_shouldBuildGame() {
    viewModel.initGame()

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

      override fun onError() {
        // TODO
      }
    })
  }
  @Test
  fun init_shouldShowLoading() {
    viewModel.initGame()

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

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

    verify(errorObserver).onChanged(eq(false))
  }
  fun initGame() {
    loadingLiveData.value = true
    errorLiveData.value = false
    factory.buildGame(...)
  }
  @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))
  }
  override fun onError() {
    loadingLiveData.value = false
    errorLiveData.value = true
  }
  @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))
  }
  override fun onSuccess(game: Game) {
    loadingLiveData.value = false
    errorLiveData.value = false
  }
  @Test
  fun init_shouldShowScore_whenFactoryReturnsSuccess() {
    val score = mock<Score>()
    whenever(game.score).thenReturn(score)
    setUpFactoryWithSuccessGame(game)

    viewModel.initGame()

    verify(scoreObserver).onChanged(eq(score))
  }
  override fun onSuccess(game: Game) {
    loadingLiveData.value = false
    errorLiveData.value = false
    scoreLiveData.value = game.score
  }
  @Test
  fun init_shouldShowFirstQuestion_whenFactoryReturnsSuccess() {
    val question = mock<Question>()
    whenever(game.nextQuestion()).thenReturn(question)
    setUpFactoryWithSuccessGame(game)

    viewModel.initGame()

    verify(questionObserver).onChanged(eq(question))
  }
  override fun onSuccess(game: Game) {
    loadingLiveData.value = false
    errorLiveData.value = false
    scoreLiveData.value = game.score
    questionLiveData.value = game.nextQuestion()
  }
  @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))
  }
  fun nextQuestion() {
    // TODO
  }

  fun nextQuestion() {
    game?.let {
      questionLiveData.value = it.nextQuestion()
    }
  }
  override fun onSuccess(game: Game) {
    loadingLiveData.value = false
    errorLiveData.value = false
    scoreLiveData.value = game.score
    this@CocktailsGameViewModel.game = game
    nextQuestion()
  }
  private var game: Game? = null
@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))
  }
}
  fun answerQuestion(question: Question, option: String) {
  }
  fun answerQuestion(question: Question, option: String) {
    game?.let {
      it.answer(question, option)
      repository.saveHighScore(it.score.highest)
      scoreLiveData.value = it.score
      questionLiveData.value = question
    }
  }

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)
  }
Game Screen
Wulo Cjjiek

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.

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.

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.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now