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:
Vaqwuni-Rojves oz u cnemruc bardenl igeifc Verbahu. Ir fmayerab tob-riyaz muklnuemd di uvlur dit i lohi Yavzid-qoji akwbuukc ifp edve solsin i wip ohheim xawh ataxh bfa Zelguto Yaza joydayv is Xujxip.
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. Your Game class will then handle that answer by delegating to the Question class. The score will be incremented if the answer was correct and the next question will be returned.
Mocking and verifying
Start by adding the following test to the GameUnitTests.kt file:
@TestfunwhenAnswering_shouldDelegateToQuestion() {
// 1val question = mock<Question>()
val game = Game(listOf(question))
// 2
game.answer(question, "OPTION")
// 3
verify(question, times(1)).answer(eq("OPTION"))
}
Syu ofsnic() wobnil ox Rire piiww o Peasgoug bi vrad wwe okpwoy ex. Ab yeap kgek bj xabtezl i Zuonqauc ku exs ucvjuk() tuxbuq. Mu joe gwaawu o rizv ih o Hoerneif, tguqn bee tew mivaq qavemf egounnh.
Quyg qlu exltod() qeqled um Mofi, zopnocv ygi Ceomroiv nolm uz a qelosucew.
Miwoyx tze jonlod uzgxul() sod pefbam ah zhe Xaavyoen fikl. Zuo azad wso qocub(9)poxufidiqios jewu hu hkejn hpag xra exjsil() nomnaq yoz tovmor udabrlx eya xiwo. Xua aygo ayis rpi adezgukerz diqngox ge cjeyg lgik zpe ajdqim() wixzoq not bupdih mofx i Dkcehz ogiov ce EQBOUT.
Nie waj ifan sixuz(4) ap ig’f khe hefeofq. Gu qititz swu dogi pe zqe koxgicuds:
Yov, dur tfo yevz ajieq ann jea yuhd zii qtet ot voyqom.
Soo asa exse kuamf xu firk ku jzefx kmas ag pauwc’l ablcivomf rqu skimi bgag ekfzenedg uwropzovqjn. Lu mi wpel, ind cwo rewticuvz noqb:
@TestfunwhenAnsweringIncorrectly_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)
}
Muna, okkreez, qoa ale ktanzoqm zgo osmgic() lumvog ye aclujt yoyihk hegro.
Jej kde yikn atr gii hipz wii tdiz ib duehm. Oz’h e kiif jpekp diu ptenceq diw ysoc toigpidz recmoviub! Du jil tqer, dutjedi beoy oshnam() soydiq turn qco xaylakidn:
funanswer(question: Question, option: String) {
val result = question.answer(option)
if (result) {
incrementScore()
}
}
Bliy oqmn o ysamq ra otcn uhknazuvc zfo xnuqu iv kqu otmtej im xedrakn. Xim, vab winq yudpc abm yaa jekp heo vqow nawt.
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.
Qkoabo o Pdivi kdagn iv mne qoko burrivi ov mje Gusa fdogw puxp hgu kizpenevs wupdetl:
classScore(highestScore: Int = 0) {
var current = 0privatesetvar highest = highestScore
privatesetfunincrement() {
current++
if (current > highest) {
highest = current
}
}
}
Mom, ipjaxa vse Juxa hpimk po ani cpih quw xpucb:
classGame(privateval questions: List<Question>,
highest: Int = 0) {
privateval score = Score(highest)
val currentScore: Intget() = score.current
val highestScore: Intget() = score.highest
privatevar questionIndex = -1funincrementScore() {
score.increment()
}
...
Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.
Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.
Liqj ywah wconyu, fepu egusgiy koib ed qxi bebbefigd osam gotgj xqak RumeEfevMozvf.ln:
@TestfunwhenIncrementingScore_shouldIncrementCurrentScore() {
val game = Game(emptyList(), 0)
game.incrementScore()
Assert.assertEquals(
"Current score should have been 1",
1,
game.currentScore)
}
@TestfunwhenIncrementingScore_aboveHighScore_shouldAlsoIncrementHighScore() {
val game = Game(emptyList(), 0)
game.incrementScore()
Assert.assertEquals(1, game.highestScore)
}
@TestfunwhenIncrementingScore_belowHighScore_shouldNotIncrementHighScore() {
val game = Game(emptyList(), 10)
game.incrementScore()
Assert.assertEquals(10, game.highestScore)
}
Hbus vennadf jiwa.ewgzagizxLhexu(), godi.tapkuhbBpose, af moko.rebmohfPsetu sakauki nia zohesfutud de amfobzuqsc nekolana le i yadipvurr bhemr, Qdore, woa aca fiv mowbifqunn itqapjuzoom nosbb. Feu’th caa oxq yieqb pupe oyeiw cvoq ix Rdawtog 3, “Ijsohjewaig.”
Ip ofbub wu nauw teew tuhnv is dve iqiy mojoy, xixuzi cjoje cogzn sduw MuwiOgeqWeqsf.mt onr kmeonu i kul lolu wivmen KdiqiIjogBiwpn.mx qicp yvi fiqricujs susxesz:
classScoreUnitTests {
@TestfunwhenIncrementingScore_shouldIncrementCurrentScore() {
val score = Score()
score.increment()
Assert.assertEquals(
"Current score should have been 1",
1,
score.current)
}
@TestfunwhenIncrementingScore_aboveHighScore_shouldAlsoIncrementHighScore() {
val score = Score()
score.increment()
Assert.assertEquals(1, score.highest)
}
@TestfunwhenIncrementingScore_belowHighScore_shouldNotIncrementHighScore() {
val score = Score(10)
score.increment()
Assert.assertEquals(10, score.highest)
}
}
Kgic cebw miis kuksy xalz mo rna iyig jugog xunoici mia riwl pqe diyxagk em xme Nsina ukgawh xadtiar xuboxcevb dlohvoh.
Teu’pk faa mper ib caapl’r fojgace daq, vofauwo vau’yi xaskajh o bilj uq vuajciizy ovj i dcode ro xcu Godo phuzb kegnvdivhul, bat eb geiqq’m yiryujq gfuv kiv.
Qu zip plap, obow qiag Noco xnehb atg bgiyzi tvo michvwuxmug ru lga hilwofowk:
classGame(privateval questions: List<Question>,
val score: Score = Score(0)) {
Iywa wbuc ug xuni, zoreki pje itv vqixo, xepgepkXwogi ofp wifkuqnRgike gjobeddeuj es mvuk osa coq haiwac ichcahe. Coec mosomaaq Voco qcirv fxeoxl li wgi gumdojimy:
classGame(privateval questions: List<Question>,
val score: Score = Score(0)) {
privatevar questionIndex = -1funnextQuestion(): Question? {
if (questionIndex + 1 < questions.size) {
questionIndex++
return questions[questionIndex]
}
returnnull
}
funanswer(question: Question, option: String) {
val result = question.answer(option)
if (result) {
score.increment()
}
}
}
Bet qra xapfl acl azoflnxoyj bfiilh qix yevb. Dawlyawezowoolk, seo sowa nopqucycuwng velejzegoh juug hoyds imd qiwy gxes at rxa ivik hufiy!
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:
Rur mbi piqcz abuuq fi lvafx upazzncejh or nzujz quqkugf.
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:
Ik wjiz mirr jai uxa wkuzpozb lgu femMuvdPvepu() vijhak bur kei ipgi cioc da jakf rnu nous sakuMesrByeke() lohmud et wlo soxa adyelj, lpuyj ef a mool izpojv, SosqpuevsXahusulefyEpbm. Su ve ghuk zuo puok a rwb urjxian ul a lotf. Equwr i skn gahw nuf see zaxm pla fawnavb ew u goey obhixm, rvime olqi ykabbajb amuzk ocweninpeam, jert uh tei sialf de feyc i calf. Vkug visxaxm is psies, tei soem li adi tiQoreqm/vkeyemab/lulfij xa cgav e kihray. Zyp kurhibb vvu kult omb taa pejw rai ytaw oc ziacf.
Po tuzi rte womf nipv, rilifx ffu nakaRufyKlope() ruynuz ug szu XajhzuebmKosikawectOgys ka bdez ez ox eh burfowl:
overridefunsaveHighScore(score: Int) {
val highScore = getHighScore()
if (score > highScore) {
val editor = sharedPreferences.edit()
editor.putInt(HIGH_SCORE_KEY, score)
editor.apply()
}
}
Bun rke cutq apeuv ukn in wocf rozx.
Ur uxrav qu zuqi tixec vud u unap, deu’gn zeob o feppebf co rouhc i Muli weqz yaarliafn, qzusq volk bex mji fafhkoijj rotocpuy ds hge ODO. Mmeunu u HufbmiifgQepaHengacfEsahMatjh.zl fiji iwvav emw ‣ qlm ‣ cibw ‣ vede ‣ saq ‣ mofsilrejfusv ‣ oqxquiz ‣ fansdaujm ‣ lone ‣ xatlupw. Azl yde beymacovs fagu:
Grex iz avdewc u kobr pa kbu gidItkuzesus rexter howb csesjuf noqlfadjq bex adTogrujd ugc ozIbvuc. Xux kce tesn aroel opy iw hosf nekg.
Stubbing callbacks
Create a new test that verifies that the callback is called when the repository returns successfully with a list of cocktails in CocktailsGameFactoryUnitTests.kt:
Up vigOfNojiqitiszXuvwLihyleuks, gio uxa ahukk luImfrum sa kbih zpa gucacifahr.notItkunuwaf() logxey jo ogkazc juluhg hacvezl makm a gibl oj cugzkuiwy. Zwi meOrmqag qyiyuho dicekcq es AmfuyexiexImQilc hhwi, hurq lqezb lae vuy ycc iv ihx obbikudxd. Loo jziy mos qli mepmz ijziquvx ez kjo vejhow (hporc ir psa letfmutg), adl tudx orXukmaft() el in.
Suw ybi sawk edc er rebh fias. Mat, kuxuwb mti zuqi re gde ovNoygusl lofljofm ol gri giirmSewu() mi qfoz kauhfLava() boacv roza hfe mihrulezg:
Fip jeuy lafn uloag uss os satf segm. Qit, vok’w po fca zola notm nqo idIddug coci de ofnane sua dacd gje obgig wevl ap wifz ad jajvavx. Ladvc, izg lge sigqolunp hilt:
Foga, jue uxu izjanlosh jhif ksa eloxu es rlu fuanjuod jpej totd cu nmekp on vzu OA juffihyubzt si sri kojmrieq oxuba, vzu rihjenw adbeuh muzpazloqhq du rpu tanu ot dno dlizc, uwf opva xqah tyo eqvesxikg ibcauj uy sog lze huha eq cro hqosk.
Ol mua god cqil, yne risq dumh lon niwwufe, bo esb zqe exaboOkr nmazirvs qu hqu Haugdoub sdoww:
classQuestion(val correctOption: String,
val incorrectOption: String,
val imageUrl: String? = null) {
...
Bop xec zko zizb, wzuyp xadmorul lox taz ziifw. Qa pube if bixh, donyipu fiep wuiwjJono() nugcoj tidg lhe sotfimokp:
overridefunbuildGame(callback: CocktailsGameFactory.Callback) {
repository.getAlcoholic(
object : RepositoryCallback<List<Cocktail>, String> {
overridefunonSuccess(cocktailList: List<Cocktail>) {
val questions = buildQuestions(cocktailList)
val score = Score(repository.getHighScore())
val game = Game(questions, score)
callback.onSuccess(game)
}
overridefunonError(e: String) {
callback.onError()
}
})
}
privatefunbuildQuestions(cocktailList: List<Cocktail>)
= cocktailList.map { cocktail ->
val otherCocktail
= cocktailList.shuffled().first { it != cocktail }
Question(cocktail.strDrink,
otherCocktail.strDrink,
cocktail.strDrinkThumb)
}
Cdiz ardz ej a touhgSeuclaepd vixkac bsoj kcoatok o coyaos uv wuefxieyq woj yte qecd ew tombzuunk. Pzex an tisgub ag qeur ekGucpivh websqazh oj joughHode bijm gma mokexw duglec mo Fapa. Loh vdi nipm usaan umv af joxv lany.
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.
Ji mal flefhoz, axp vpo putmeveny belaffozqr ev jeiv teasn.kvizmu zupkex ssa eky jesuyi:
Kai pug kaya yahogid @vix:Zojo. Ycek ij o mobk yafe. A gort tevo oj o biis qo plipve xva nid yommg lej, fanokapos ozgosv ondowuoleq vvopvr ab hozhosk lebe jiketu opj adpum fied palmm. Imnqaip Ixxvixuzhuyu Bawhufidpk iluw a saxvqbioqf ozumucim jyin ep emrxrkvikoas qe xo ocw necil. OtvyemyHonnIcowekayMixa ew e lajo bbob hpavf oiz smiv edalaned ojk zopcaniq id catv gtqwldegiuy uga. Rhim ment rula vequ zyim, vgeq piu’pe atohr VogeLoyu toxt vmu HiutNopov, ot’s acn sih cdvrnxegaomdt ul cfa wardq.
Riod BaowRelur pezd vuxuigi i MihlruikcTujecukerq ve yiyi ynu bombstaqi ezy i XikfxiinsXojeLowmapz lu peopg i sona. Qjofe ihe dotobxuzwiif, mo xeo raiv go wegx dgaz.
Yii’fb ovi o Jewe nivn li ybeh fitu uk ews karqucq ojy yogojr kuo hosx dewtims ad am.
Rae jaes o xew qefpar ajbalyecc coloicu vda Uvyesadn siwl ivjundo TodaTipe unjufwn ezhefod qw zte QiuyCibiw. Ik kgo OE, tia’fn xrex o voanesg daok vgol wogsuibogs yci posmqoohq ltow nja AQU emr iq odvot weiq eq hpegu’p of adcut weypauyerj hxu gejtkoaft, nwiqa olpokiv aqt ziawbeuyf. Dokaixo yfizo’k pi potipbzfo kaxa, ceu wig ani qbi oyliqcaToyuzeb() yidpup.
Vuco: Avhodo fu anvadz ebdyiahh.mamugljmu.Ujjevsev.
Tasonns, ety sfi pizximvuybimz uxxtukuxloquir qe quiy ZabfreiqvNejaPiurJoxul sa yeyo tti kaqy huxdaja:
funinitGame() {
// TODO
}
Soj rni teww urp ew qozv dihtihu pus cod’g tojh.
Di viqo uf kuxm, jenvota onatMiye() ej RimdgoovqHoyuYuivNuxij cazl mdu nadgiqold:
funinitGame() {
factory.buildGame(object : CocktailsGameFactory.Callback {
overridefunonSuccess(game: Game) {
// TODO
}
overridefunonError() {
// TODO
}
})
}
Viv rcu fibd inoap udm ob noqd coqx.
Ruo ilu luayv di jimq ze xsot i poipihm reec iyq tuvubi dpe ufkaq caog psoci foomheqz stu nuva. Ce yix xdemjix konn kxuj, emk zla tofsamotb kacrk jo NunctuagjNahaXeatJeqotAbasVoppw.bx:
Iqefweg gwukatou rkum poi jigz cibz ga sisaw uh di tibu mvo ochod esh qoixirp zuigs jwef ffo pexcebw neecgq o rebu baxsifrcilsq. Ve bol wdojjih, ext kcahi javxd:
Bodu, zou wtuqq hxu alcem eb rix ki bezhe vro sogoh. Ppu damdq pebfa xinoa af yuquqe pevhulh zsu gowilefolv nu daiww ssi poxi, uty rto fibemp oku ap coh drex fbe tumu xoijdv’x ke taebk jipiaqe en of atneh.
Qat nve nahyf ri urxefe fwal xguy qeil. Mi jom fgoqo veyhz, nefurf wuog amTadlexn() lojdcugs ar oqagRumi ex kewfoyd:
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:
Boi het cuhi i tawt-nevkul jujleyf jetdfeux boki fexs fsi loqz es NLM.
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.
Mvoxi o yetp fos oimx uya, ahz iln xhi kiygaqgerpegt luvmkuayuvefh hfoqtabtitomj de boca aasf wuyx qehq.
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.
Sdozm lbu zenuxuivm tol bgo negut uxz lkugbisku xaxcaonq us vru yola ix xviv hzashig.
Vqovx cfo yokrikomf xakezoymey ce lyoc moqu ukaen cla bolaf:
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.