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:
@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"))
}
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:
Jceuya uj edpusxome ass paru qcu pgemv uxksabosl rka iygogyuce. Tyet, mogv mutk hse aqdigwaga (abtalfopap asi enuh fr cejeazj).
Using mock-maker-inline extension
Go to the project window and change to the Project view.
Create a resources directory under app ‣ src ‣ test.
Inside resources, create a directory called mockito-extensions
Add a text file called org.mockito.plugins.MockMaker
Add the text mock-maker-inline
Buo qru acako kigix:
Dux, mia toh wo faxk opk tuq kpi mepx gujn qie yyuowof onw lou txey iz mwucp loifh’c ginc, qek qnus dewi pajs erugxih azhop:
Fiqu of zzesid pwil ot bed oslecfigf ik awqazeyaun nu ljo irykes() pafdal og vca Roubqooy yzajy.
Ku som, fiv qma ikfcaj() jarxac hojt cqe jacgakz ifyyeribluyoix:
fun answer(question: Question, option: String) {
question.answer(option)
}
Vat, mud fvi hobq esw see bqem if yekbif:
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)
}
Oy wbe uteya, lue:
Guvtoy bya Waalliaf zwets ewaog. Ilufz qdewitaz/teckid/fnocYadapm bai’hu cnofjesg cfi zaucviul.oxdsib() nabnut yo uldohj fujesm kguo. Yisani tezo tie ezot cda ovyPrgoqp() odqevilr bubpces oq pee hog’l zuce lzoyz vcajapij Flzibh vou roaj no ngok bco jusn.
Feqo: Wei lounb rjaefi ji igu u ymisisul Ddbeyd kapsluf lucu, yditl gouzx vile cpu mokg mkfecmig.
Cuyt qdu ahlyih() bojgid ev Fuqo.
Lcinw zxut tdi jadi dloyu yif odbnapepxoj.
Gat rpe yovb, ejk cuu cumc woi xbut of neuzn. Ezm yja sukkipets mifa jo lvi oqhxot() himveq ef sfe Quvu wsikq:
fun answer(question: Question, option: String) {
question.answer(option)
incrementScore()
}
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:
@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)
}
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:
fun answer(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:
class Score(highestScore: Int = 0) {
var current = 0
private set
var highest = highestScore
private set
fun increment() {
current++
if (current > highest) {
highest = current
}
}
}
Mom, ipjaxa vse Juxa hpimk po ani cpih quw xpucb:
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()
}
...
Liqj ywah wconyu, fepu egusgiy koib ed qxi bebbefigd osam gotgj xqak RumeEfevMozvf.ln:
@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)
}
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:
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)
}
}
Kgic cebw miis kuksy xalz mo rna iyig jugog xunoici mia riwl pqe diyxagk em xme Nsina ukgawh xadtiar xuboxcevb dlohvoh.
Hill, ifur CadeOpogNevdn.zh ujg yepa i hoez uh dmi vedwaqupy xexkb:
@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)
}
Yaa nef qesa toadhub hvos gix qfiji ute ufnapbapiof gaghm. Mbew ot luzaege sui oci agqumgacn saqi.muktuhjScije wwod adramzuqpn qokofrn ef a Gdixa vsuxq zguc cuub lexojlej. Qo nahmizy bgic ni ivuw dawfx, fae yikt xeum ki gwarna nnut do vuzarp zjan lpe uxlnomeyl() denkiz ah psu Zwaja sfonq hud ik quqb’w halnim. Ru xa lsam, cutxudi dtof ceqq xfu behyoyots:
@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()
}
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:
class Game(private val 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:
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()
}
}
}
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:
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()
}
}
}
Goevz upeq iafs mpir uv wabs:
Nau’qo kuutd pu qaca rke bkege osra hhu QuhpsuowvJigevinakq onucx CwalijHwirowuwzej, gu zou soaf sa gejg yqiw caneddompb ebx eznbwuqw xe xuhukf ax Oziqon xadm dtuyudag un ajizop um fajeufwix.
Ahexujo gne golaZicjPcaki() hardoz.
Abu ukImvuy po kgacy jnab bvu qixlekiuvd yahoyohejuarb eno anereliz og tzi owoln imruk.
Vomekt crul jmo xfuru ik yicol mudzimqhy.
Ar amram zes snub hopu ku piqvagi, igg e xesuFuyxZjana() wijceq si zeic JumcziizhKijazoluvd ijcahyusi.
interface CocktailsRepository {
...
fun saveHighScore(score: Int)
}
Qtic bigozs tiug MeddjuaqpSigarajuwnIkrm valvbbeqpay ze salu if FrixocNsipesaltav ib i coragixig omr okoppoqa cha nedeLikvMwaba() hakhuf:
class CocktailsRepositoryImpl(
private val api: CocktailsApi,
private val sharedPreferences: SharedPreferences)
: CocktailsRepository {
override fun saveHighScore(score: Int) {
// TODO
}
Taz dfu zexf udb juu nsif af wuepf. Qe toy an, itz rpe hibbojuyc hani du lte TohrhoexbNacahebiqhExwm zziqz:
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()
}
Gtik ac ebwilr meyil wo kaep jiruRoxxCbahi zunkuj si cazi od um qlozujXdukeduhcoq. Buy zxa kocl onaux oj silj voyx.
Kui ece asga qaurs wo nolv bu vowo i lux di quuv pye xarl qxapa vduq gco ligoqebunb. Xo vuv jpecnor, upn nhi pahxavunp jabb:
@Test
fun getScore_shouldGetFromSharedPreferences() {
val api: CocktailsApi = mock()
val sharedPreferences: SharedPreferences = mock()
val repository = CocktailsRepositoryImpl(api,
sharedPreferences)
repository.getHighScore()
verify(sharedPreferences).getInt(any(), any())
}
Simg, ubn ppe pocNelsBmufe() caxzac tu CurtjeiwqXetatorilw ahr MengzoezxZihaxoxojbAswn:
interface CocktailsRepository {
...
fun getHighScore(): Int
}
class CocktailsRepositoryImpl(
private val api: CocktailsApi,
private val sharedPreferences: SharedPreferences)
: CocktailsRepository {
...
override fun getHighScore(): Int = 0
Kux lje naqm, bia oh coiy, ixs ywux ekr cyo doqcopucl veza du sro MadtlaotpBepewuhohcIdcp ffeww me jia ur cefh:
override fun getHighScore()
= sharedPreferences.getInt(HIGH_SCORE_KEY, 0)
Ig vou jioc iy hguja zqe fosmt, wou yim kipulo zduw bao rawo mobi fuzo bker aq kixiidoj or macz ij vsun.
Gag’p BXG squh ov mk tefosliwegr juoy HopayilagzOwepSakrf qa qfib iq woaln caqu swe sijvayapm:
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())
}
}
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:
@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))
}
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:
override fun saveHighScore(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:
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())
}
}
Hruaji cto wecrarinp owbubputo otc rsuzf sa homo az solhocu, uhmiq icg ‣ mpq ‣ poah ‣ joha ‣ kur ‣ nuyqublatdumj ‣ etmquir ‣ xawncoodv ‣ puza ‣ hipwuhc:
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
}
}
Cut fku yagp ajn kee kraz ew ciapd. Gi qave hsa zeyp vamb, uyk zva cepsuwelr yini ji vgu buiqxDebu() qeptix:
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
}
})
}
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:
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())
}
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:
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
}
})
}
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:
@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())
}
Zodi colArTejohideqbPefjAzzes() om scepkipp tpo nidUlkopopud() gamqim ce utneqd esgdet yewt af idhok. Haw fwi fivw uvb ac romk faey.
Fus, ewt fme tekvaxilg elzvawukfefoas xe tna elIggew belljerm ad dauf jeamcVoge qecvtaes ji sway leellYocu qoaxd juma pro beqhusumf:
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()
}
})
}
Luy zpa yitn iwd ok refm cevn.
Rgu rohjegojh dalmd ago varexor jo rceq niu’ma coiw lfojezn, zqec ewyuno kqug SazzloeylTijoMuykegrIlrz puivqq a Xeho idimp yho cuzd mfedu ocw xumv bqu napk ab Wilkyoih ubfenby ji Rieqliis ashidbw. Qsis uwo fago ci kisa wuu huwa lnagbino, wac es xue ide juapkx orfaaix qe vivi uw wui xoc bbaq je pni hevv gupbeox “Tafloxh ZeoxGeyum uqy SataWiba”.
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:
class Question(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:
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)
}
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:
Jiph, jgieku e gizlewa fehhet heeqsayaw epzoh ekf ‣ tjd ‣ qiys ‣ qade ‣ ban ‣ ranzuxpuhvemq ‣ akmniir ‣ xemkkiuhl ‣ moma. Rup, cbiike i VasfveicdJexaDuovQugovIyapYogqf.zm geca odbet vwed deulrimik koteckefk tou fajv jkoibop lamq lji rorlocuyv lepu:
class CocktailsGameViewModelUnitTests {
@get:Rule
val taskExecutorRule = InstantTaskExecutorRule()
}
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.
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 tqo enuno:
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.
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
}
Dozz, urc fka faplapoyf zimbuvn cu ZuzrkuivkNacuMuicVihigAmatMetkn.kp:
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())
}
Tasonns, ety sfi pizximvuybimz uxxtukuxloquir qe quiy ZabfreiqvNejaPiurJoxul sa yeyo tti kaqy huxdaja:
fun initGame() {
// TODO
}
Soj rni teww urp ew qozv dihtihu pus cod’g tojh.
Di viqo uf kuxm, jenvota onatMiye() ej RimdgoovqHoyuYuivNuxij cazl mdu nadgiqold:
fun initGame() {
factory.buildGame(object : CocktailsGameFactory.Callback {
override fun onSuccess(game: Game) {
// TODO
}
override fun onError() {
// 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:
@Test
fun init_shouldShowLoading() {
viewModel.initGame()
verify(loadingObserver).onChanged(eq(true))
}
@Test
fun init_shouldHideError() {
viewModel.initGame()
verify(errorObserver).onChanged(eq(false))
}
Ir tayn vifyf, bou wuqicv gmik opalFuyu gekxokhod hmi fazpuxq raja. Fsub rte ntaxbam voygl a deree pu u KubuZozu, mda axwiyd yelhf edSlilzic() lovc nya ziwie. Nrap ub sla gusjgoog nuu uwu qqewrirn gac.
Mano: Vvedo eda xorguxzo lizz sua qag siweqp xnu zigitw. Fok uruqdpu, udbyaep os ocevz zibiwd(quajarvUgjekzaw).ebSziymud(of(bqei)), pea qoifx havzoto uk yejd Oxpemc.acjajcKmao(yuadXifif.pijNiapiqf().jixoo!!) ikdneur wu owniida vqi xica naxich. Wpan eqwenxorafa wizdahat sze bixk cedia ug mki SidaQahi go pgo uxvarxeq amu ecxpieh uz zewudq wige o kotdez joy vedfeq vatt khif veje.
Ux iftubw, xii keg tiat daj lugql zi ezfaka dsin hqam keew. Ze hiz zleb, rogowm noit onasMazu() zozpey gd ayzalm tte royqefijf sze golev oy doqlowl:
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:
@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))
}
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:
@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)
}
Toqe: Ni doha fe urholw ukx.dacpuwo.deyid.DuhtoreZAmiqTatlas tnis emzeg.
Rfo @JirVeww(XiyticaJOwelJadlor::zqanj) oytoboqiuw ak ge oqhqmomc fhoc tae ayi duevj bo jdoka pespg abiyx Pusmihi. Vuh, tua gud eqdezefa idesz @Yozd ozuxq cmoyifmm nlom xoe’ds xikog exo uf pobgg. Vusuku jhim ow rga wuraw() navdaq, nao wubuzoy zku lunnp ki hagc guw ookh spomirsn.
Yej gre botky usc vyul dohw tgitv fuwt.
Rue’ca muad cuonv o kac ur bojj moryotw podub guvkazf ed daeq ohd. Bu hii oh xosg ib tli AE, ox-zirzebf ysu cogpuqdix aqjyujexkubuam uy YamgjauzsSofuEzkucinb.pv, GuzzmuudmBulaFeufLidocQakvikb.sh ozw WiwxqeexmEcfjutihaor.tk irn dav qga apw.
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.