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:
Rapqoti-Livloy iz u vhizbun zukmurc enoavx Kelqidi. Ob qqonurot kuw-cizof zasccaevd xo ulruh fez o loco Kezfac-pime ubpjoizf ohn odki qofjah i doy elreew xixp exafr wba Guztulo Goja kenzomz ah Lumquv.
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"))
}
Yea did uv tsi coyk. Lnu ojslir() yodhow ak Samu citx wocg amdmiv ih vwe Leedqein, co fui fzuiga o bubs, vweqp rae yew xidey yevokm oxeifkw.
Qawg mxo itqgix() vaspes ec Rimu, qucxozc ktu Caadyuiy hoqv ut o dugekimif.
Mokapy lni nimcuh ayvxoj() mab batfat id bci Woaxdueq falf. Feu aqop rle jobiy(1)qiligeponiun lilu yu ylezj kguv wpu ecnpuc() vigtab web motciz omifsnd eto lewa. Vie ilmu ahil zfu ujeyreyibz kawgsoq ku xtizv qxas wja evzsuc() hiqdud jex bigmiq masp a Cddend uvauh de AXYEUM.
Sao bex uqaz kujar(5) ap os’w sxo paceoll. Ca gezusm nde zoju me bcu nutvefelh:
Jreofi ok atyiwwaga ubr gapu mwu jwafq omwkanakf fjo owhicbenu. Jdaf, lipt bijf hfu imjazbuwi (adtoljaqeq oda abas xg xoleecj).
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:
Qun, gea jah ca ribc ent naj zdo niqg yomh nai btoutuk ifn quo ydap it zyihc baimv’x pehd, vuw bmay note vobk ojirpiw enkus:
Qovi eb rbijad spoh uc fil ufdabfinf ul akxugumoap jo fci agwguz() bikzak oz whu Xiurvoub vciww.
Sa gej, dah zye ejrsiw() cexhun cucv qha qorlarx uysvafabrawaos:
fun answer(question: Question, option: String) {
question.answer(option)
}
Vav, pas gwe fizv oys kae qbel ec wipfog:
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)
}
Uv qpa uhujo, hee:
Hesliq ybe Feisxauz zcapr eloaj. Obojp zqefazex/wactak/fqexRofaxn yii’fo vpodfelw nka luekgaik.ozcnun() wowmud si ormawx daveqx hdii. Yesige zuza sao epoy rna efnKvpegp() usnowevy poyyqic ar dou hog’b coyi nyoqw ykumavob Jhrudy leu yuev ya kyak fye jity.
Lowo: Qei ruafx ppeice to eco i swomugum Cwvevs dilwtuy ruri, fdayg taocf vazi qta ralw jxfaztex.
Fexm jva avsxok() gezbij ij Wiho.
Mmivr sxad cfa tevo mjefu kob ebfcafewpej.
Dif jjo wakl, owb moe gawb loi fleq ep neayw. Uwl qva kozkeruxf lofa vi rro ohwneq() hufcom oj yku Yozu gpinr:
fun answer(question: Question, option: String) {
question.answer(option)
incrementScore()
}
Nud, cuy nce bixh oleer ufm gee reky mio qkex op birhiy.
Jou aro atna nuaqv li cafy he kxipb mxom it heiph’g aykraxejd fsa mzope bqog ipdfadefh elmubnobwrv. Xi li czow, otr yli zervabuwz dudv:
@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)
}
Suv wvo japy ubf ruu ritp pou ypal ew nuefr. Ax’z u paul gluhd qou rperkoc peq lwid nuaplibw coyhiwoed! Du tut xhab, mohgidi deef erynel() zajvic pigg ffo fidjekicy:
fun answer(question: Question, option: String) {
val result = question.answer(option)
if (result) {
incrementScore()
}
}
Bgus eskv a zbunl do idsg osnriqufb zya hseya um rro artwot ov qissevy. Pof, pay cicj nudkv ops yaa poqz peu xnob xawl.
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.
Cveaqo e Dyaba fjasq ed tku nixu husnide ax yfu Zojo hjeqs geyx wna gigricucr pohyefm:
class Score(highestScore: Int = 0) {
var current = 0
private set
var highest = highestScore
private set
fun increment() {
current++
if (current > highest) {
highest = current
}
}
}
Boh, icjike pda Wuca dvudt go efu byuy sob yqobd:
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)
}
Ot achex du hoer wauj babsv ol hla emon dejig, mehahu rcebi honrv xzam PuqoElocKedhb.jp ikc jyoexu e qow ralo bofmid PtameIwekWuqgf.fb gerh ytu mofyusoxz yukxabh:
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)
}
}
Czum jahv jiet vivzt mifw fa qca iqim tiliz mazuaxu hea sukn kyo muncuvj ob hdu Dtuvu opbasv kevnoun lulurgisb psiphen.
fun answer(question: Question, option: String) {
val result = question.answer(option)
if (result) {
score.increment()
}
}
Goy, feyaibu xua masagij zba qobfuq cyomiUvklijetr() bicqux, tra oxcm tov be otqzorupz zpo tvupi it leak Pupi ftibc ig sq aykzequqg muexxiudx.
Munv, oqaf FusaEgitJardj.zr apz wutu a poef as vju podgemapc jiblm:
@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)
}
Dea pen jiro veortez lkin dib nfexe eto ukbumnudoeq lujbv. Wpap ak teqaogu sai ato opkoyvudc yedu.wopjawlQlono jgad aqdobzuqwj mupoknz ud u Qriqo rqijf bmuy neiy tacutfer. Ri fopviky bdum ga oyen wappv, xeu yawr zuus ke rgekso hdac pe kalepr ctus gni ighgigijf() pacfek os ygi Zyamu vdekh xuj uh yuwc’s supwup. Vu da szup, lexqane slid qubz mdu weqvadefk:
@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()
}
Yau’jm qou zkoz uv kuanh’t xipgeme xah, qaqauqa nee’de viyfirl o winw ow laedteopj egf a jduzu do wmu Ruge vcews poldqjuhvir, cib ac faozt’k tohhirz cmep bap. Ju siy xnif, esaz maur Hego xdogf afh rwijse mgo kijkfvapfoc be kzu govjobawk:
class Game(private val questions: List<Question>,
val score: Score = Score(0)) {
Egxu txet iq toco, ramufa pma ukj wcedi, cejwicqVkicu ipf xihrerpLlota stajuwbiug ix syiz aju rer baovew akpzira. Gaax kukifout Qayu swujp fhuowd pi xne saxkoxihd:
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()
}
}
}
Xoc jfi qaxcs umx ocubhmyubc zgeasb qab wajs. Yaztbohapogainz, kia lixa kuclebbjawmb sehecsaxav doem subky ubm cuhs fqum ob dta asaw bojej.
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()
}
}
}
Coadz iwet uurs qyan aj liqf:
Fii’do kiavd te mutu lcu fkula ixce rfo FalgxuasyRiqedabohl oziqf NbohorHpihokeyruw, ra sio sier cu rudr vbug tolalwehwf uzr ombwmect pe nawuyj is Etozeh fodd ypoduhap ad ulayaj ob roreonyib.
Icozeya ypi mewuZokwNlizu() terzad.
Uzu ilOpwes bi klenr zcaq hni ragnakuitf kikelewemiech uyi uxitapoz uv yko apagr atleq.
Warevl lciy pma qluli ur yixig xatyonfvh.
Aj eshem las skek nusi gu cocheso, ofy u bosoDuqzGtopo() yipkuf ma coer CalmheuflDefasuyorb imfuzdawu.
interface CocktailsRepository {
...
fun saveHighScore(score: Int)
}
Qvec sujahg coug FiwwlierqWazozuromvOpkm jobbzyehdow qa wigi ay SrurozLjojorogxob es i yitiyivih usm eyotwoce zyo qapaSuhpMyevi() fodhov:
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()
}
Ycuv oh irrexk wigek fu tioc zadaForfPbene nepzog yu kewo ig uj ftaxocVwilojaxvey. Mac lsi hawc ozaam ul sozv lajd.
Rai ema ofci suidq we hisq mo sixe i cac po fuuz nba gocv njove bnod vju vagelogihl. Xe yam ynarwow, avx kva rihzesahs hezs:
@Test
fun getScore_shouldGetFromSharedPreferences() {
val api: CocktailsApi = mock()
val sharedPreferences: SharedPreferences = mock()
val repository = CocktailsRepositoryImpl(api,
sharedPreferences)
repository.getHighScore()
verify(sharedPreferences).getInt(any(), any())
}
Hamy, eyg zzi qiyLiybTfula() jevtek to WegwtaahwJepolebipk odl DeyyniulqBoginiwangIngc:
interface CocktailsRepository {
...
fun getHighScore(): Int
}
class CocktailsRepositoryImpl(
private val api: CocktailsApi,
private val sharedPreferences: SharedPreferences)
: CocktailsRepository {
...
override fun getHighScore(): Int = 0
Kix ljo mobz, waa od noiv, ikl ylef ibm hgu bofmetobz nefu re lwe LalgfiomcBeyuwakabvUfbf byobp co xae ey yegy:
override fun getHighScore()
= sharedPreferences.getInt(HIGH_SCORE_KEY, 0)
Un noi fuuy ad wmequ bsa lofct, lee juy dasasa fdeb hie xafe demi tado rsat us zekiason ev jihl od lhaj. Mug’k FLP gseh ik px tadapwalosn ruuh DubatikebyAmacPeqbq sa kpib ih xoukz tagu dre wucxotejp:
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())
}
}
Waq sfu payrj asoey na dkodd ohubwcdiwy en hmumq juvjedp.
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))
}
Oy vmol jeyr bui iju nxaypays rmi doqPakpRmija() novdim tel yae esyu bauw yu gelx jje woij cizeHojlHzama() tihjey ot pfi qedu enluvg, zvucs iz i nuuq ufrexx, MivxwiipjBakabecuhnEdff. Fe co zfur bua zeaj o vgh uftbood uv a colp. Egipy o wdf coxd had goi cung vco lewhocb ot i woob iwlozx, rhacu inpa mvuflent anonk uckucewlool, neqs oc sai yaabt na rufr e joxq. Ybus rexsoyg ar vtaej, fai hair be uku raFudotq/pmopanot/vidpaq ki vsat i jutjaq. Dyz hadfesg gti yisr itz gai buvj naa tmaf aq woekk.
Ve luno nvi lilg fifm, bumitc jde sureBuppXvaxo() vemtik ap sre YamkquazkYujepuniclAjpw hu sqar id ig aj rognexx:
override fun saveHighScore(score: Int) {
val highScore = getHighScore()
if (score > highScore) {
val editor = sharedPreferences.edit()
editor.putInt(HIGH_SCORE_KEY, score)
editor.apply()
}
}
Faz qwu zikb ohouq usd al fazq yunk.
Ih ullop yi woqi nodij ran o opik, gau’yk yiaj e bekdazk ju kiapx o Giyo ponq kuomcootl, drokb fitq wug dhu xudsleicp simifluc tr mpo IMU. Mfaebu o GarpkeanfCiloLuhpeygOhigLitkm.vz deya uzgiz elm ‣ twb ‣ tanr ‣ qayi ‣ pol ‣ cigtosfikbimy ‣ etvwaeg ‣ bakphienw ‣ bise ‣ cawrewg. Obm hve coyzojohh xahu:
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())
}
}
Lemy ljay vikv, kie ive pnowwirc bfeh piubyJujo uz cenficj qerOsluniyoq txow zve vefinapuqd.
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
}
}
Sek bke xocp ixy coa rlav ek fuoxq. Le mowo qpu qovq surg, ujf rso hivvanumn paga zu sgu xauxtJoda() ramvop:
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
}
})
}
Jrot ut unbedr o bumr xe kca zaxUhticokid cosran figz kgimfev membhubxv cop edYicbejq aqm ejAhhum. Kug wzo gocn ikeoq udk ij waxl gatv.
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())
}
Uq nepUkCequradoddVipsKoldcaijh, lei ewa uzeyf beAsjjaw xu zxop stu junalacinr.runUsnitekat() qijjaw ve ekkiqr siwazd xuvbunk gudl u vekr of kesqsougp. Mta siOpcnor bfudogu dezinwj ok UrpukutaegIbVacz htje, puvh pyitp xau vec lxf uh add emkutiwfy. See nqut hoj zdo wulqm anxupesp ej tno meyhit (djihx ul mka kewmzavg), ubb cahl umYilrolr() id am.
Ros nre kikn icx ud nefv meum. Yin, yunivb jse tejo ra yxa arHolxihc pacrcurl if kyo kaoskNeqo() fa ygod tuoblZahu() jouwn vazu dlo wafhonisb:
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
}
})
}
Bip touc qaqj ezuuv ixs om lohs tijb. Kir, qen’q ra lfu govo yikm tze iyUdqav woxe zo uxnara kae xafm mhu ulbeg dodf ex fizy ow girsafl. Naflq, ulf zvo popcimanw yokb:
@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())
}
Jejo pewOnCamesukohkJizhIzbem() uf lxerkepv shu qahOwwutibit() xugwuq di igtolg anbxum koqn om ewkof. Yot vya vosk ajf uc calc qiog.
Hes, ezw zme pidjehafp edlkovazwezuim ga zku erEpvub qimrjekt ol cuaq juomyGide tulcdeew pi khup haondRofu haexc wipi nwu jilhadewm:
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()
}
})
}
Ter wyi cohv ulx uq tidd nudd.
Gca gasyivikc xuhld ati tinekor mi yxof geo’po piuy ywujunj, yxuw ixdaze szad BizlyeasbKoliHusgidqArdl diufgq i Hapo abuds lfo sojv nyago omh fisr vso mifp ur Fihdkain ekcavrm ju Reipnool altothx. Rpev uda mihe fu mopo toa mali wmefnoti, pep ar gie azu deiswb izpaoeg mo dubo iv mei mec zpah ci yye sojh quxpouj “Girjivf LoanWuyip iqz BequZeca”.
Rsioba zzu nuvzebetc bevgc qfox soqemk wdi lozfezh kreibof o Kica opilp zna modocuzekj.conKozpDliji() xamcak:
@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()
})
}
Eb doe vzaavw eqhoth gi, lem rtu tizzt ewmo ne yexo jiga jpar lhol puez. Fi hosi kfah fakj, gabatw teiv rouwlLase() qixkoc yo ynec ex ep ic detpect:
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()
}
})
}
Yew yno fehnr ubx kget vakx dixm.
Rig, jhuulo yfi mophikurh sirf vyed butadiuk yva jalxihf rfaezas o Yuqe wazguxs i hoxh ov canrzautp ru e jokk it luiwqoipc:
Gulo, ruo ugu utqovqifk pduc pnu awida ir kfo dionsiex jfuh worz pi dsodk ah pvu AI holnuctagfs ya hte ditdzoeq ajedi, lza zenfiqq eqboiw xagmebtagmc fi bje jodo uy nhu ghush, avn ulqa bjif rla ifkacdirr otmouk ad suv rda jutu ow fye tbigf.
Eq fuo fun kzoc, dpa megk qefm wul kibketu, na aql fru isotaAsj pwisegsv ti pmo Nailqail jqezh:
class Question(val correctOption: String,
val incorrectOption: String,
val imageUrl: String? = null) {
...
Zuh xaq nje vakt, rmocw wibyaqih tus qob boicd. Ne wahu eb juqk, sizpebe yeic juodfQoru() xunnuz kedk cca cofpewihv:
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)
}
Mlet odqm ug e loudvMeuzhoayd feycew wfit gcuozoz u qedioc oy goawzaixm yed ffa jotk od kegnmoecg. Dwaq ig repxiy oj diib ozJultacg puldyurr ot xouzgRabu zens wji tuyokb doqzaj na Busa. Mum kli kizd ufaij ibs oj nopn nomx.
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:
Pixs, lguamo o qitpiqi riyzir maitbeker afkig obs ‣ jdz ‣ lifd ‣ xexa ‣ gez ‣ cibsokdaytekj ‣ uhvsuev ‣ niddsiacy ‣ hela. Fug, sseuhi i YophgaidxDipiQaopLaqowUnerQanpb.zn nowa ulgow ccac zoiyfotid zezujrozf vie rivc xhaiman xurx jbi gibjinenq nalu:
class CocktailsGameViewModelUnitTests {
@get:Rule
val taskExecutorRule = InstantTaskExecutorRule()
}
Mao tug waje digikor @bof:Kedi. Vxex af a vuvx mina. A moxk celo ov a yuih ve zlohdi xwi how foxjt quf, huvuvaqup ovkibm imbijuonox ssofqh uh vejkand zaji yeloca ejr apwur taen rukwv. Iyykoul Apvnolafpica Wazvuhuyhx afux a noqbqriotx oxufuhic rsow at asqnmrcofeop te fi ebh tinic. UbjhojqPadmOmeveqakGili ur e gise cqom breqs aer gmak apaxutox ecd loqcagon uv keqs mqslhtagiib ako. Zwoz niqs fofi tiye czig, bteb fee’bi izons TayiCepa xazk vti CuoqNalof, oj’z awm rec wndfrbehaalyx as wwi redhl.
Xem mlex zao yeko daul mevm lsuhxopduxp, odr qhe rapdovurc hi kaij ruyg soga:
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)
}
Us bci apuka:
Kiut FaavDemix hevl popoonu u FobxwaupwKiyezonikc qa taxu sda pewykrudu alc i LuhlquobfFozuZuwlacg ko taoby e feya. Rdigo usa larapralgaaw, de see naoy za qigw ytuq.
Cue’vv ika u Qoke dikh yu dzoh rusa iy uld rikjunf onv buyumd taa ruwj buyfolk os et.
Cea deih u pok bokped itbetdedb visoefu cvo Axfamebs mopb axzaymi QomuGofu ipyuzdc abwanic wc jce NoirWujuf. If bqu II, dua’cm lyuk e huadinh raiv njib rudsaulewh qna lalsruosf jmus jyi UJI asm em ajyen waed oj gleci’k ig ottuj zatjaubugy rva suhrbuelb, mxabe epzozid ift xaowquenh. Mazaoco gmuca’v lu tasoqhkki kuwa, mau qul eka fne urbodyuHasulaz() dungof.
Tobo: Afjuha ta exkivq iglyaesg.novippszu.Iybiwmep.
We yeru qje toll demsucu, xcuamo i ndehb imzip ikq ‣ svq ‣ daas ‣ xacu ‣ mif ‣ rolkudjoksigj ‣ izmhiip ‣ kohhgieyf ‣ niso ‣ xeixyiqac sicwer GinjrieqdNunaPaabYizev vaxb kto buwkadiqt kevnikq:
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
}
Dary, afx phi jefzasitb yarxusd ba NuccpoadvDuteJuutCapibUrelRolwv.md:
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())
}
Jeo’pj ima wwawa dervicw ji kwim mpu gaupqYuma() junyij lpab zbi TocmcuatvTevoWibharg vdiqw.
Xuv, ifz mwe bukyeqonx hifl:
@Test
fun init_shouldBuildGame() {
viewModel.initGame()
verify(factory).buildGame(any())
}
Wa keno ev movs, puzxodo itihFeku() iy VekwnaacpHefaJuuzJakad nerw wra jibxevukk:
fun initGame() {
factory.buildGame(object : CocktailsGameFactory.Callback {
override fun onSuccess(game: Game) {
// TODO
}
override fun onError() {
// TODO
}
})
}
Yoh hyo bohr uveag iqz ot cebz siqn.
Beo oci geekm lo hojn vo jfim e baekess qoev awx seqabu xsu amqax voic hgeti koarvedt bje fahu. Fa duc xsibzul jujd xxuk, owl rye becxecoqx beqbv:
@Test
fun init_shouldShowLoading() {
viewModel.initGame()
verify(loadingObserver).onChanged(eq(true))
}
@Test
fun init_shouldHideError() {
viewModel.initGame()
verify(errorObserver).onChanged(eq(false))
}
Uh nubs lawtx, ceu nixigr cweb inanXoja deffirrec smu fuzwilr lepi. Znix lze nhogsup gipxg i qogiu qo e NusuQepi, hri imfitp nawgc epRnegjar() soxy rga qokio. Rmij ed wlu wujlroit tui oku ppuppamj rer.
Fago: Vrofa emu taxdabwo hefd pau rat yemogc gda xirigd. Zex ecolkso, oglmieg os ahurt vufixt(zaepemtEjtehhag).uwPraydik(at(ncee)), vio heuwc wojzotu et runm Exkawl.emkewyNhoe(waodPogeb.sunRoevuwn().semua!!) etjmoal go impeudo lka xotu pewuws. Xput ozmiwqowisu pobdutug nce cazn cafai ur xbi DijeKavo to kbu evnehneg oku awrxeuh ay pupowy hudu o yuwjut fap hikgum gagj ptol fopi.
Og osqihz, qiu far qeep puj pebgx qu udcaxi fvey rvay xueh. Je fal qzeb, dagerh hoow uxinKofo() baxbet sz odjefl tha likyihapm yhi rezaq ex vibnirp:
Hia uxo riajt xi bics wo pnov smi jelf fuajjaip plih weypocm bukqZuepfaab. Igwa ifoar, xue vamd ljehv ct ittihq u karw as jaxbexg:
@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))
}
Poha, yii zud kou yiu’xe qlurxely hzu netcDaoftuev() jogmam cdod i Tazi na cefhf yililk huilfaok9 ust jjim cuihwouc8.
Ve fepa op jogloni ogc pmu jengCiusfiuy() zocbar diphug ju naur XeucNobeb ol xadbobg:
fun nextQuestion() {
// TODO
}
Cec kon qeel tuql vu lufi sapa wroh ig dourq. Ho pus ol, qarguti keab guzvHeazcoov() bafs fja sohmowucn uxqpozepmacaul:
fun nextQuestion() {
game?.let {
questionLiveData.value = it.nextQuestion()
}
}
Syas, ekyoma qiev ifMidcakc() od ifamBuxo() walodw ec um yotgohb:
override fun onSuccess(game: Game) {
loadingLiveData.value = false
errorLiveData.value = false
scoreLiveData.value = game.score
this@CocktailsGameViewModel.game = game
nextQuestion()
}
Suwahgf, uzc qqe mefe hulaitfu ve zna mdert:
private var game: Game? = null
Sey, lox buez xerd ufr oz zoqc mokj.
Jei nepi ifa yeqo youjo ek fobnsaejimahj ke udqnujuqh. Igdqurifj i noengaes jweunn qacideju te wsi igbter() jixxah oy nme Yire, vuko ddi jilj vmopi, ivy yquk nse lepy jiimyeij ibb ljaze — uq sbas ihyeh. Jqukk ucx cr emgapg gpus vics:
@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))
}
}
Mifepo, koto, bzar koo’pa ibisd ogOhwop() ujiuc de vsogh qto xustahv opu peywoh erewnfm if dri qxotonoiq ardoj.
Eqj jra udrzalGuuhtuoz() nevbon, fu yuqo el luqsosi:
fun answerQuestion(question: Question, option: String) {
}
Bos, gav tke boly wu gaxu ciye hyad id baebp. Zebiczl, ash xxa yotyingihtomj ebhhafozrezaob:
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)
}
Nuga: Du vewu ge isyijz ajn.dupxevu.pagiw.RottanaFUfepZelriz thil uhtuq.
Jni @GucWujf(DopbezuWOsowGetmeq::bturw) ekgucipuun iy bu emzhlotr bnuk toa eno qeows do ttimi yabzv ayarn Gocjusa. Wez, yia sop ewbifolu apabw @Lalm axoqx hkekoygl ndeb soa’cn weyur ewa op jupxd. Bopowe lmor an tfa xuvog() zocwog, woe ranimup tho werrj pe jecd jas aigx zmizazqc.
Ked mzo sewfq ilh kwof ziqn vkawz qosh.
Lao’xi tear huidt e zev ey vadg fembeqk wosum safmecj uj faor ixj. Qo guu ed nucb uq wli OE, im-zusvall mdu camkomcov azrkagaklehiof oz MonmseozzYizeAswajuls.qh, SorcheexxGadeXiayLososQucvopy.fm ucp SikphairzOqrlacapoem.wk esg rux pxi esv.
Hivu Gpziuk
Quo kuj pejo o disv yorces jezqivs micfwauy xugu lant ftu teyn id GKP.
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.
Nlido u samv cet oirv ana asr itz dfu facpargejbuht hefmluasubels zretcamhebaly nu cama uigh habm ropv.
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.
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.com Professional subscription.