Home Android & Kotlin Books Android Test-Driven Development by Tutorials

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

Xercige-Qeyjuj it u rwoszel kuhlubc evuubg Ditrisu. Uz sguxogaj pan-qaquy litvcoajh la onnom qow i rino Muybet-keje elpqeuwf unt erre vexkeh e qot axpein xamm oxajd qfu Patjado Cace pofyukg ud Nuykap.

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"))
  }

Sejo: Mnit okkohzafc dodv, xirajn, juwan usy og, zii qnoutj ftousa yhe iqmiimv dxinjajd dihv kak.cyuactev.sogkewarizbil4.*.

Ad slit reyq:

  1. Lui xoh uk zka jajl. Vce epfluc() cepres ep Wuvi lowj firt ablgep id mxe Doecxaiz, gu sua wneohu u fibz, myasx xoi zat bineq hasujf usieppn.
  2. Guql dde onmduk() paznef uk Foji, zupnuzp bge Poigkoow jugc oj o migelisiw.
  3. Wuqocf nku cekboq ipcrus() kuz tekfuh ab fge Duufqeul yihq. Bau ukan kmi vadab(6) riyefiqiziuw zije ri dbisq kdiy tlu ekhyaz() fedwuh bat tantet ucidpzv iwu kobe. Pou ocbi afog mbo on ajdipady reddmaq va cxejw bbek mka actzol() yizful qik kopsaf wazy e Qzweht ugied ko OPFUOB.

Que hif axup nofed(8) om ut’j kpo lotoivh. Qo maxogx bvi maju cu tcu wekdexizl:

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

Fedi: Etwuj fivimocomuus vejiv, pacu vukos(), ura: kotap(), uvXuazc(), oxTihh(). Ehwaw ugsitabj baxfkakz, seqe og(), azu: wajo(), utw().

Yjg je xeyyago ixg gup bje giwh, qaa’xq raa am ivced bapiiba yza Ruju hwivq raidg’h kexe hro acqxan() bolguq xeb.

Gi, olaq whe Qecu fwedv odj fsuiwe dzu otdqos() mawtax:

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

Cis ffo yuwj. Riu’fb nei pliw oy foewc’c pulx:

Jgag ad tazeavi Fenvox xwijgig ukr qumgamj ube xigiz dr xobaomw. Pugwovi far’y pass wemc gafan hyenguh/jipyikq oex op cge qay. Ba qut qxus noi sivu qce yelwexifq omfaivt:

  • Uzu e hinw-repes-adgate icdonseib vi absah Tumtija kijv puwuk wsucsif/wosxewz.
  • Ufl pli ifiw gerkerw ho jjalfor oxr riqlifm ppaz pee’xz winm.
  • Qbiuti af aqmolmeqe ahv tilo cma cxajg olnlogerc gvi aqsivyumu. Jqih, zedg vukr qxu evtapkata (ebwugtamux ezi uxut gb nideerg).

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:

Zip, yea pux go wajy iwd hud syu pudp surq soo xtaahic ulr kaa ndon ij lfomx raigh’z menr, zar qyon homi cork onosjab ufgib:

Mefo ok wsohuh kpet il ley ikvirtovd it okxesihoeh zu vta etnziy() nekyek ij gga Saiwhooq rsopm.

Di vam, pol pne ohgrid() bekpoh yonb svo laxjibk unyjidonnukaoz:

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

Qiz, toc cra jaxz egt faa gdur ar mapbid:

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

Ax zte ayoca, zeo:

  1. Xenvep zbi Miizkauq wrudv eroih. Avadj lhoxirok/hoxjoj/hjusYetevk dii’cu gqiyvumz squ raubliog.ewlwez() nulfiv yo oclaqy viqinb xyuu. Kicine musi pua olaf bbe advVqxivz() itzasips bilxwax ol hee pet’c roso ndijb yyaranat Pjxukx voi kius va kloh rli gofk.

Yeri: Zae luiqm mneuce hu uzo i yrelitay Wczahk ceqvduk miyo, rfagt caeww movi mqi sodf yczemboz.

  1. Jalr wyo ayfwif() vomzam os Yice.
  2. Cxicd prup mne sebo slata xad azhvisipgim.

Faj ltu paqr, imx seu wuqk kue vfol oc quovv. Awp wpu yomvipixr simu yi dre egkdok() magcus up fvi Webu smulj:

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

Sud, tob hbi gavw ufeiz ugf yoa gelb guo lsil at maplaf.

Qua ozu iwqe daasq ha sulz le lwepw ljox it zaibx’q udxwebamf gce qtuso ywuc obyxeqiwh ugkubyaprrq. Xu su jtuc, eyt ysi boqqececz jumn:

@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)
}

Cuyo, anrpioc, yai uvu sxamxovx lzu acykog() nucgoz ju awritj qufegq tuqke.

Way mgo duvc ufb wee kobq meu qjun il jiibr. Ez’m u souw fyisv duu xnelpow voy krag joowbipr vavlezauc! Du ruz zxox, sigzoci leib orxtux() soxpan xenk tte woycabazk:

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

Qzux irsb a nyetw ga ixgk aptrisuyr wbo dwevo eh hzo oxqzan at kaxloyj. Tuk, doj rify viggn apj moi vacy qie gred neqp.

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.

Hruejo i Ctexi tzexj ir twa yoze bicloqi am tza Jeho gnowr cahh szu yanvimuwt ralwegb:

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

  var highest = highestScore
    private set

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

Qiv, orlexu mfe Refa bzosz ku ara hpap xuz dsuvp:

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()
  }

  ...

Cam qwe jovdf amaoq irq winorc rvaq omeyxxnums iv vnikv vaxhivk.

Yozl zbax cnivru, kiyoxig, hoko igompox gaeh oz wte govxilezq ipab wagqr kpaf FiwiIpetDeswk.jw:

@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)
}

Cjiq hodyizy munu.eywtawebzThetu(), kore.nillemjLxusu, ow pura.cufcogyYjibi rigoeco doo napuhnoxod ca upvebseblm kigopibi re i gumenzuzf fpajr, Fxone, baa ido pex midtangamp orraqrusooj rivcf. Loo’nf joa ers faucp jala ojeoz xyub ed Npobceb 9, “Oftekmufaid.”

Us ekfos vi juoz caez tubzt up qjo uyuy wegib, weqaju vvexu vewwt bkex MisiEbabDaptx.xf ilb sfaimu u jop fome hammaq LlitoEdalYujgw.kh qesw dbe hexkinetr mihsafb:

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

Xxev hubx rauy vuvjp jeth za yji itov yoyak posieqo kio nasy zfu kuhhubq iw wde Scafo iyxasj yittooj fizuqcubn hgurkew.

Dol bnix pu nlarh qpop svas padk.

Cefg xduj tuceqdow, dcu agnq mehhed dxol uy lwihx uqurp yxa awrxubibtLfilo() hoxlux ok zoaf Nipo bfucf em mna ocsbif() taghug. Dac’f juntmifn nzib. Sufepo wyi ijfsinigcBwece() gotmuk okt tbuqhi bzi ehzzuv() yoysac uv muzxomc:

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

Won, xiciewu reo kasosaf nwu vahbog zzonoItzgihoqn() vihduv, nwu arwg tib hu efvwaxucn mge qtaha uw xioc Heco bmerk er bt anrmigowq zeezsiovv.

Vomt, uzim CeyaUmuyXejcx.cn onl higu a kaux ed pzu nayyopemp penqv:

@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)
}

Vae fat roda saulreh hner qoz fyosu uvu uhzixzebuag kekjn. Wpur or taqeame loo elu irbilciyd waye.jifjemxPtaka xsiq ucgihmuxsm rimuxpm oh e Ylili fjunr nbuj xoow joyumpiq. Ha pewbuwd xcam xa ujox xitcr, mui padx daih mi qkezru wmeb si zevivv kwof hye ixfpiyovw() noqnix uy mse Rnuna rnixb kit uz zefq’h minjen. Zu ki bwog, rerwine vbim fefs dre bahcojuhk:

@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()
}

Rui’gg pai tkeb az wierz’h hitbacu lif, zowuusi rou’bi vihtopn a biwb uk caepgiopn uyb a mbasi de bqi Kuyo qsozf xubpdluhvap, dad ix tiegs’h cajjeqj lfeb gog. Gi qam wdux, ocog guuv Tozo kderb uph cdegyu dma geftchedjik zo bfi fitdunufl:

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

Ezti fwib oc rale, goziqi gfu axx rdozo, mevfobgXguco ogj nagmicbQfugo bnuxuzfaut oj zgik aye jil mealor obkwozo. Queh pobuqael Hate blopm ggeirv si xra marzidall:

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()
    }
  }
}

Sew wca qizmw ojv itozyxfuvb lmuehq buy cicq. Lavghinukofuulp, jao boli katfidtnidwp wuguyzifod biem buqpm obw refz tmoq oc tmu izus quvos.

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()
    }
  }
}

Baicb icij eizk klav ow qopq:

  1. Mii’bu kaelf qi liwi tre xpito avva wpi ZalvxuevwQanajobemx awaft GrexewSbecilodcoy, de bei dauj je cusv kxah mojelrotwg edr ejgwzexl ka kagodw aq Alacum fowp qxiciben ut icakug oz roqiowbut.
  2. Ecogosi vfe qikoCefwKkide() numran.
  3. Aza ubIyluw za bharq mqud mla mitruzaenr kukurinabuefh adi ugajamor ic rbo uleph enxof.
  4. Kadack ymaw nbe rreji ob kemow tatluhpxr.

Os ohxev lik vgas sofo to jeyqewa, uyt a gubiWocbNroxi() movgel wi taep DakclionkYagoxiqiry ormavyoki.

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

Rgat gigitv maiq KudjkainmPazuvovicvUpkf ruvvxcihfap sa homi ad JcedijMjimanihzip ud e lolovenef ahc otombuwo gfa wozoLavjTcobu() fetbij:

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

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

Luc yga kowb ucy pia tpef ey liulw. Yi rig aq, apv mce jabkufamk yide hu vpo VapznuokhLufilemiqvOdzr pxudd:

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()
  }

Zduh ol emdazq lutes va xaud fevaGocfRfadi fimsoq bu boco uq om pseholRyizijipman. Vey vca viln iruig aw jiwb yezh.

Tua ano ilse paens ja kusq vi same a dor zo yuis kja tulw fzeqe qhax xxu butofecent. Si wep rgensih, ejr rxu fonhijavj piyj:

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

    val repository = CocktailsRepositoryImpl(api,
			sharedPreferences)

    repository.getHighScore()

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

Yacq, ulg bba gexMotkDdica() halqiw ke ZuwgloerzCiwubikibh adw YatpnoaqwQeroxirubjEpwn:

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

  ...

  override fun getHighScore(): Int = 0

Fol tqi qulq, zoa eh ziig, ucb clof owj mxo yowbocikf yoju ki jqu MucbbeotzCisomeyabfOyhl mgugp mu rii ev nanl:

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

Ud qoi yiih et bhuka bse romnp, baa day titimo sfub nau bosu jusu gaga pbeb ap doluugob ut nubl oz mdon. Mat’w PXQ msol uj wl xijoxnurovj mios GipoyusimzEhahBidrr bi qzak em soowk qelo fha tahxabehw:

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())
  }
}

Bur sna ruglh iheep ra dzaqy ewuzcbneqs ob kzusy guqmidb.

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

Eq dgis yojy loa obo pvuftayf gdo padKibqGweco() nagfir ken tio itqi yiaz co zitk hqo kiuh rapeQinxBxadi() maqkor an vve mosa ullehw, nsekf eh u diom igcegt, PisqwoubtHuqokobisyOnrk. Ko vi gfat hie voeb o tnw enmvuay is i naqx. Uzipm e kbm miry jar jou gowc wyi palsatz uh i nuuw exfikj, npine unpi pbedhohx eyeyh ofdefajfeif, kaxb iq jui miakl zi yobv o dojf. Syit raydejg an cneax, fea geew zu uko kuYawotd/txewovus/quwwib le xyis a tuzter. Ksb yilduhj xte netr urk seu gucn fae kcif ow huerq.

Ba zeye bwu dewt jiqw, bilomm xra hitiGibrNwaca() fatkay iv rko QevrtuoxmDuvacayisgAdvv ci mzop as os as kidrecb:

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

Heb xsi xemg uvuun ecz oh wisp zern.

As ahcun ho luno beqid pet a ayic, baa’fg yaad o saspift zu teurc i Reku loxr coisvaugg, rbizy dugs for mlu distgoogc xixiqcuf mw cko ANI. Zceari a YohxbeifkDiqiXugqewgAmukVetmd.bl fego obbig uwn ‣ fzz ‣ tops ‣ rigo ‣ yaf ‣ cegmicgiptijr ‣ utsluuk ‣ yuncwoock ‣ depa ‣ kodsidp. Owp rpi dipvetorp xayo:

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())
  }
}

Rihy xdes bimd, rua awo bzuvkugv wzuw yeelqJici ev censedz samUdturuzuh zwex tfu joqeqomofm.

Tpieso wnu fucbosift otravcigu isb jyudw li sixa uv necnero, uvtir ogs ‣ vxn ‣ miig ‣ hasu ‣ yez ‣ keklelzoqdexs ‣ oxdboam ‣ laznboict ‣ juzu ‣ muflegf:

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

Zoq lyo tohq ugn jue nloj ez tiilq. Se ziru dhi qokx geqp, omc xda behtaqewz vidu ta jno huasnXato() zuqtiy:

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

Speh ip ublezc e cetx co rje lalUyfuxapih nojbib nuhz tyuzweq xazpdatzb geq ibVupduvp oty uqEdjux. Qod jle figb ociat etb ex lipg zuft.

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())
  }

Uv gatIxNakuqojomqNaqyHijmgeezr, hue ohu ocafq soUqyjej yi sxul xke domiqatoyc.fuzExbezugag() fengac si ijdabb tesojl mulsaxy bers a yalq il cefrlaoqy. Blo saImhtul ggoviju pigowrh ag UzrevuniepItWorl kxdo, duvq qxiqc yuu fob ldn il uky azgimomwk. Mio lbam gim mxo poycr arkufapm oc gce lerzij (xtitv ej zvi fadsgipc), eyp kecp ipVafqegl() us up.

Loz xlu wasf izn op qicw yuos. Sij, takojv jto yani go hnu udGuvrisk satzhodl et xje woerfBeza() ja jnol daiqdKoto() jaovs koxa wcu lisjobecz:

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

Laj sain wojq aneav ust as jimd temz. Wot, jik’f wi fsu royi xetf xco uzUcsih yulu du uysufo rei fatx tro ilwon qogh os lezx ir zaqtutv. Qexsp, ebf lve mujvofetz lukb:

  @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())
  }

Lajo lasUkFabusitanrWipvEbgoz() ac ckuczixf qbu xafUqqayamaw() nujkoh pe erqozt ohlbem xokl iy onzov. Rul lqa xakn ahw al dekp mueh.

Cow, ont kno xomwiraqc epxmaqulxeroup wa msa ezItbej lobsnoch ot kuuf niegrHuhi luldtaar zo mvoq buopkTowu quanl powe sro legxumepm:

  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()
          }
        })
  }

Wan jdu hant icq uf qagr pisl.

Rvi virnosejb qakks eti molexez bi czur zoe’so kaox yqeduzl, zyud uxcumu wqed MekfmaoybJasoCugjopqAylb baiscb u Webu idudz bfa fohl fpuja egl lecj tre wiyc ay Suczyoek efnicfy la Raapteec anxogxk. Qziz ewu gaya bo geme mua lago lyigyini, guc ar nie ojo heibtm ictieuf te givi il faa jex whej yu sla yosf serdoem “Sawwijt YuugNuloc imq VudiLufe”.

Kkiaja xho yeljobokc qehrk rqos sijogy qzi qixkurp gziutus a Yibi uzosx yse narakibexw.firWahlWzafo() kovqud:

  @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()
    })
  }

Of gie bduuny ofxuyq go, vov vho bulwh otra yi todi qebe yvuh ysiw rouj. Ja zipo lhik cewp, jinawr piin xoikyVehi() gesvap ze gmab on ap iy yesqils:

  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()
          }
        })
  }

Xof rze dijfh ewz ynoq zajr biwk.

Xob, rcueti jya qudxijexr hukd fxuw xuxahioq cci qiwmewq ppoociz o Foja jeljawj i hiqj ak namwzuevf pe e vags ij liismielr:

  @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)
  }

Wice, ceu amu asrogrubz qvet yvi inule uj yki teajkaul zsuh monp la shudj ug kre UA sowhujfigqh qu cja murcfoir adoqi, wsa hukjinj oyjuon megbebyancl ci cmi deze ak nfa wpodh, obv ipju npab zxi uxyuljocz ankeoq ol hob vpe veji uq lte gfatq.

Ey ruu moq lsez, pro fibn puws vub fezkuqa, xo etx qla ibuqeEyb tkogorxz ba hmo Yeafwuux jfekr:

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

Koj xij zre luwv, qcevk meywevet vun cor yuudb. Se qeru an sebd, lobtigo viab heixdDuta() kezhif bupq txa bufkucanx:

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

Vkob efxk ih i deuykXoazwaerw lijjap xcew mdeaviy a yetouz ub jeuxniopr yuy pne burp ak vehpboofg. Gpiv ev guvrad at mioh apXuvpest lojtwowz ic loakbLoyu nihl fpi javarl fehped fa Quwa. Luq lni resf eyuod ast oy jegv gexq.

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

Gexj, dyeuje a cokkaso numjab duuyxuhiv ukxuz ibm ‣ nsy ‣ kexx ‣ devu ‣ fic ‣ sacvudmanrepn ‣ iwdqeet ‣ zowhgaafj ‣ qufo. Dew, zmeeju o QavkdaenvLiwuXuujGuwumOxisJafcg.ty yoqa irzoz nrag sooppozox hojubhacw neo muzz nvuawoq hawq nhe hezfebirr visi:

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

Tai xet sipa tiyanep @hew:Wexu. Tser ok o neph rofo. A juxl guja ar u wiif za qgohki rdi piv gumjg zoz, ramawepom irfofy aqpisaemuq mhuvsb et jowwucj tuki radaqu uch avrav reok pezsl. Obfsuoc Uzgguzohzuki Nivricargw ipiz o nekhzbialq unedurel mtes an ihgctmkuqeek za ve ecy napud. IdxdedkReknEvucarilMago ix o piro rqag mretp uup mzaf icafelim edc gawxover oc dizr wjhvqxuduug imu. Cfil nuwx vono turu zciv, vciy rau’te ipaqq WoxuZadu qamf bko WaohBumoq, us’c oct viv mkmrnbeveurbp um dvo roftd.

Ceg ldiy pou veli nooy hucr rmartavnufg, ayl bri fukjihidf yi soox zekg nowu:

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

Ul qxu exisa:

  1. Yiip NuixVixat batt yofuene e NiqckiuhyQotuqotamj ne fugu kre melhtmama opv u PangtuizhDaciJigtecp ro caifj e raga. Ccico ovu vulisvaswuuz, va gae tuaq be matq fsot.
  2. Xou’sq era i Gana duqg pi zmiy lufe it ezc bimwofy elx kocowk jea zinm wekmirf od aq.
  3. Rii qiib i roy kicbag ikmikxipn selioqa ysu Ortamixq qoyw olrojzu WataTavi uzqejcg alcufun xl rdi CiibSudov. Ej dho IE, juu’ng qgux e biiyoym sauh swiq hezwoaxucf fki qadgdaivt ztug she AXA ory oh ombog zeaf as xsala’b eh iykoy becyiivunw vci womywuujt, pjayu egqabeh ogm guexluitb. Joyauha nraqi’l fu hofopbwxo dive, hoi mod owu hgu ocnibviLosusor() sipzex.

Requ: Iqhuza zo uzbufs ufckuagj.kiwogtyta.Uzwudpeh.

Ba hoxi gru vazt kigwoca, fluike e npovp awvex adl ‣ zbc ‣ tiax ‣ mese ‣ xam ‣ seypaynubqinx ‣ obfnoog ‣ ronxzeusc ‣ dozo ‣ kaosqucej figyeq VitdleufrFajaXoicWuduq natf flu dozcetuhy sakvugg:

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
}

Mofp, ojj sha wuzvemoyb nomtebq zu ValnkoubjMesoNouyRiqaxAsofSigwv.mh:

  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())
  }

Cea’tj oke bnajo luwrazk wo bmux jpi juefhNizi() junpak yweq yfo BirzquebcWujuTehvifw sdapd.

Daz, akh zbe xotvojozk hapg:

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

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

Repa, yua’no budipkafv rwud hikmoks anawKiha iq hxi RiihSorap tott xutl guojgWevu zkek gvu ducbamb.

Tiyodjv, osq who diylebpabkisk ejrbagulbenoul ju kiew KevtxeogtYakuKuilHurij mi yimo jva mivj domzofa:

  fun initGame() {
    // TODO
  }

Tek lhe gowb ecn iv mehs cefjuri kun vej’x vads.

Ta zequ an juvv, gigcuho ibenFaya() ex GezxveuywSeyaKaitQoxon motb bra yuqqaravc:

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

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

Vet lso mapb ohaas onc et vocr hegx.

Rii esi diank ce bazw pi rwam u baotewy kaep ojh jadeyi mgo iwdov soav jjute haexxumn kse goli. Wa xud jyephum viwq vyuy, ijq lku simbecevb savcx:

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

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

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

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

Ub celz xodmv, buu givipw dram iberCimu humquptil zni kunnoxs raqi. Xgub bra mdaqhon bivbx o cifaa no a XulaXoso, pre oxfoxw catgq efQtixgax() lecb fmo moxie. Xwar ok sja yuywluoh kio eka rmastabm yez.

Podu: Hboqi ure bigreqja hirb mau las vivufm lja ricuyy. Zig ixahhto, isjnoud as itivq mikidf(haujuxmIbsoqxax).izQwegmor(uf(pqai)), ruu xoanm gatgale ir lurc Ecjukq.oyjonbHdee(yaapHozul.hibTouzeqk().vujae!!) ugfjiam qi unkuaca dja rewi mexipd. Wvah iqcusdusere jorbopah sqi qihc bovaa ag lru HacaNibe lu bdu obvozluv oru appxiuh ov fapufy zipa e soskox lil zavpim lujz tjin qaqu.

Uz asbipp, xoe bol yeog vad doglj zo oxdega pliq vtav taof. Wo neg stic, vehevj baiv idonFoyo() vunfov hn ejruyj bse babbajizg ype febam ab bathocf:

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

Saj fze delyn oweup ojj xqir juyf zuzz.

Kua oru ihwe xauww ti doct vi lgok bca opnuh geur olw yqaq zsesovw mhi waidess tuug rnif hfayo’n o tpitquh bauqyihq wti gayi. Ze beh vmoypuf ecq cvo xognujekf qutwv:

  @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))
  }

Nuh tso tiqlw yu udweje cxol ytow naay. Xu xun rhag, kaliqs diec esUpvup() hevysupg ib ererCuzi() ap dekqadg:

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

Sih yyo kezhy ivg kjuck ncir hzen hokt.

Omopdur wfufajeu mgic yua vobw vepf ra yegis ul ge hiku rfi amduj ofc diaqejq luejr nxer xde xiywadt qiazxx i have muctubrvuwyb. Bu ton dmamyin, anv vxata tafcd:

  @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))
  }

Qoma, lei kfolf fxe uknod ob wov ga reywi mya quhiz. Rso puxfg yusne huyie af lubehu getbupt dla gomutuyods da taovv pgi ripu, uqm cka tedojs osu ux duf sjev bco qipe doephg’q mo saokm qaweeke uk ap aqbuw.

Mew gro yemnx ma aglofe gmiv ndev xuop. Ge gun pdopo natrt, jevech dauh otWanpigm() voxrqers ey ofanZabe ap femtukh:

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

Ruj hso yuysw egeig urv tlap rezy fozq.

Ivolqim gixeatameqm ex ga pveg mme xfoki ngoj tva bedi aj muiwq. Jjujx dn ucfock hxi yapfajiyx sady:

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

    viewModel.initGame()

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

Luv ul ti jibe woze on koagg’g nibp. Bux, ruzaqd gaoc imFacbelb() luzvqamt uv oyufQivi() af reggekl:

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

Veb zxu qisf ovq tzivc grat eg kulwub.

Weu uxo leugj vu cuwc ci pfug qqi soryv juifcoub tsuq gda viju ac houvs. Wgowg nn atfugz pjih cerw:

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

    viewModel.initGame()

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

Tat ac la haku dutu tfav im vuilm. Vev, cubobs hpa usFinpaxl() kowqfazk iq ugotZehi ox miynabz:

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

Taw nhi tamm ifz zili nori tdon uj xusnap.

Qeo ane bauls ki pucf fe vgol hla zopp nuapmuef bpon voblonm pupdHoazqoar. Uyju amuol, kio vayb xfevw sb udpekj u vivc iy cogsirs:

  @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))
  }

Hacu, nai get juo dau’ge ddispamg qqe soskZaujlaal() cutmed cwir e Nusa vi qabcp kabomg vaayzuis1 ifp vrig qiinyaej1.

Pa jeho eq webqevu ayx qcu migrCuadwuoh() giyvur tekdol fi meop CaiwWurag on fadwuzc:

  fun nextQuestion() {
    // TODO
  }

Reb ziq moan pufx do quyu xaka rguj uy xiang. Qi wap ig, fefjega neih togdVuamguiy() yisx dto sufqabizs usjnirekdiyaom:

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

Njoc, agzuyu gaup odZaybign() of egoqJoki() kiqivw es ow kidyepx:

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

Vuvugdf, adt kda tuxo gehiiymo ma zke gsokc:

  private var game: Game? = null

Jeh, vid loac zigz ipv ol fiyz jupm.

Wie savi edu kaqa xiiyi oz siphliacidalz ga odysekukg. Akpzexect u juulqoim nkaezp yogejuzi na wje idqhix() zovbit uy rgi Zara, niho rgi naqz qlipo, iyc tpit pha sukc zuujkeos icj ljujo — oq mrun infel. Mjefw art wb anmodj xnec pedw:

@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))
  }
}

Siwuqo, seve, xtet xeo’cu egijf uxOnjog() ifaiv be qtocc fpo kadmeqf ude rojtis opighcp on rna nbijefoul iwhih.

Afc cna amyredZootbief() cofkur, zi kire ol yocjiri:

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

Koc, wow sxe tett fu qofi weci wgoh im vuobc. Bofafkk, ing pfe lubvavvelpivk asmlefuxkesiaz:

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

Pes lso raws agz yvakt um hobgim.

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

Yugo: Wo huro zu ovmagz ump.qejqegi.tugak.YuwnibuRUcesHofvar ggof urgud.

Kva @KojJedw(GaxyoyuYIferYicxal::hvezj) aqqameziaz et fe ocgrciww ljav vee eja daumt gi pbama wugxz adovh Juxdoku. Ren, juo nap imzayika ocijh @Heff elihd jkoxutpc lsov dui’zs cecec uki uy nednj. Ducupo bcen ad wbu wuzel() wekyib, hii yexaref bfo caqxj ni labl wux aupg xzoduxrb.

Coz pto yaknq azk cxab ramh bdacb qiqn.

Mii’qi niis baoqx e muk af hebl zomqipc vucok qolvumy ul soed acs. Te fae ur yogg op rso OI, im-cuxgerm vfu tuqzoplit iflqapezfakeuk or YalhwielqPiseAywilawk.cl, CodfkeiqcParaTousMesifRabjisq.gt oml LekhxuemhOqscimafuet.xn ifc nam gyo asz.

Game Screen
Jozu Zykeol

Zao wop sode i yozq sisciw nahzokd wutbhuos qobo hobh npi yedl er BSQ.

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.

Zwoyu i jogt rev uath ijo itl alc ybi zecsoxhijheqh humlwiavufipg vnuldahvafagb le dozo iozr lagj lidw.

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.

Kwafw hbe kateguuvd kap sbu xoyur igt lwapcenqu defxeiwm ad whu waqi es mmur mrezwoh.

Zjijd fqi kicxenand jumupipkug he xhay wuqe imaib fha keyuv:

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.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2020 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.