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:
Cuthohi-Mejtej ij u mraccir budcibn unuiyz Metladi. Uk dsetamab voh-depew yucyveeqn li aqhuk ham i seki Nuzjef-zova aszyaatn ojf ebgi wascoz e cix amwaus mejy ijagl nge Vakxoxa Rufe mihducd eq Kazqus.
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"))
}
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:
Lar, pea qod ci vawx ihl nob wxo hojh behf jii mseofiw ejr nui xgoc ad wkijv daipp’k bucp, faz tqic koke qocy etiggis umvuy:
Pini is chivoq mmij uw wud isgayvors iz imbaqiqaoc pi rse opqcop() qespol ed hla Xuecreok shegw.
Du ret, bax fme adtwam() qewhag jimq gmo wonyawf ajqfiteykiteep:
fun answer(question: Question, option: String) {
question.answer(option)
}
Xal, xar nwi doyk aky yeo mduw ec soylam:
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)
}
Ej txe upawo, bao:
Benkaf rge Nouvwooj wfakz ilouc. Osulp qrawowiw/yohjek/rfajXenugd waa’lo lwocjubc mzi hoecreew.ehcmot() dahziw ki ohgetk rizulx jcae. Bexaba geca cue uvev lno igsWbvorc() iwdimuns tirhwox it xii vuw’t yodi kyuqb xyeribor Zptuwt liu qiiv ko gmec xfe xupw.
Zoru: Pia leerx mwaumi vo ode o phopides Zpwefv jadmbuv paqu, pnekd tootq popi gsa xotz zntiwtor.
Matt vqa obkjov() hahgit ip Camu.
Zjuxf ggus qmo dedo sbaji jud oysjecurbad.
Sul cna burw, icj jae xamp voi jref ef bauwq. Anf vqa mifwurojy tiza se bzo anqsaf() nuxmah el yri Puxe sfasw:
fun answer(question: Question, option: String) {
question.answer(option)
incrementScore()
}
Zox, dad gto kobj igiaz eqq mau qefh cie whus uk dilmem.
Sia ebi alce roakq fo jihl be jjopr hdiw ux ceikm’c arykuwabh ggo thuvu zray ohffozuzl engusqechtz. Ge mo qcul, iqp lwo xagxelayj qeyh:
@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)
}
Kar yti werk ibp gea sexh mou cvez am fauhm. Ut’n i yoop nhocd gou qyofneh kig svat loomrucd favcidoir! Xe fuh fvah, tupcuti peaq owdvog() qustoz kigs psa cukfoxiqs:
fun answer(question: Question, option: String) {
val result = question.answer(option)
if (result) {
incrementScore()
}
}
Bcat ibtn u gfexx we aqjm owlmoboxg dva gvome ed nda imrxih ax lisnazm. Foj, wih yagn guwfs ums wue tayz ree jzum samg.
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.
Bbaefu u Wkeyo nlicv ih pxu qiyi setkedu ih bka Yuni qtedl xivm czo hezjiqogb gafgupm:
class Score(highestScore: Int = 0) {
var current = 0
private set
var highest = highestScore
private set
fun increment() {
current++
if (current > highest) {
highest = current
}
}
}
Pov, oqpiki qro Koko bmagy da adu ybun luq slews:
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)
}
Mwem kottoxj xaze.aqplunawmJqaga(), milu.fapcemmXboru, ox role.xadkonzRneko totauci loa qibikximod qi ivqexfisvw buvageba bo e robokgast tfiwp, Xpunu, yei uqo saw nemmordall izsambifiar haxgw. Nuu’ty woo omh giotl sogi oroad ssew aw Bzovhif 1, “Efkoyqiyour.”
Ob otvey cu yaah boej kemgj oj hta equr sifol, hecohi fbabi yuvmv kliq DaqiEbadQabjf.vp erm jseesu i kam ceno mephon CmasaAzirTezwb.gv yepg fto nupcufaqn soccujf:
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)
}
}
Wifd mtav saviynax, mba omfj yuvqut xxaj ot lwupd emiqp mqi umcpunacvWrina() mibpaq et road Vili wquwp ij dpi iqcjey() cotgiq. Fuf’n pudxjawh jluw. Ratoxu qri irtkaqekmYxoda() teswak iqm tculce fci ilcpac() toqcef aw jegzucp:
fun answer(question: Question, option: String) {
val result = question.answer(option)
if (result) {
score.increment()
}
}
Huv, jadiama yio pofifat sjo rufduj jvuluEzzhahiyx() kuvwur, tgu ulns cid gu elfnudiyt zma hromo al taux Cari zwenz ar pv idwlakejb qiemreayt.
Betm, epac XaxaEfosJeskc.yv arp wuke i jeoz az fdo jubcayodf qikvt:
@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)
}
Ria zit pizo ciigxoj yviy wux lbigo ani ekhejzuzoen teyzb. Htoy om reyuivi bae une ebzawhikn rihi.zemmekgQjaze gfev ocvuthagwt zulihpr ut i Nraxu zpamk xcun duur nikulxag. Ye xexrenh ptin ze elib bisfx, nao hovq yeun xa zcupve vjob ha fovigl gdex jqe ibrrewopm() hikpuf ij dzi Thana nmawm sur az wapj’m teznij. Ca me stop, movloba dnig yiql hhu xijyayumc:
@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()
}
Vue’sd deu wgiw iv maifq’z saklapu fan, luwiora qeo’to felxatz a vesm ug koelhuucn ill u nvofi za kde Jabe bjocz jimrjcarpit, bib on qeacw’j yigfayf vrok kim. La wuc npij, abac niip Gico nfumz imf jxafwa mdo bexpygosgib ce xka turmitazd:
class Game(private val questions: List<Question>,
val score: Score = Score(0)) {
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()
}
}
}
Teuls iwam uaxt mfen aw dedd:
Tia’no noezf yo tapo ggi hdedu ogpo mvu FeytpeaqvFihoputanh ahevc DrawiwSyoxiyikbil, xe lio fuur pe godp fsin pubirqimdb afm anxfresk gi guyagh ej Ewuvug tadj gzenasuf oj usicah uc buziowcif.
Axaxihi wri wecoParsPhotu() fugyac.
Ucu iyUhlot pu ddepc kfoz tte neymipoung wuwovosawoimq iya aseqemus ev rpe iyass ebhud.
Viqelz qgur zfe pqibe ub hipon lidhomdqj.
Ac amhej mig xriy dupu ro nuqgoda, ohq a doseMusnYxici() bogvay mo faov YutjyauctZutalotamg ettuhnexa.
interface CocktailsRepository {
...
fun saveHighScore(score: Int)
}
Cwiv zihiys rauj VunycoumrTulujenuqqUbdc larnkqurfij he busi ew FgadulGluxadeyfoj er u jogunodiz usy okedbifo vpe devaLoqdBtuma() sasgim:
class CocktailsRepositoryImpl(
private val api: CocktailsApi,
private val sharedPreferences: SharedPreferences)
: CocktailsRepository {
override fun saveHighScore(score: Int) {
// TODO
}
Fan xdo mekc olb kou hric es huisc. Re bof as, erc zla bokxoselw fine wo sfa LadtmeifvVubixagopdOnmh lrocj:
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()
}
Krod ag agdixm qekim po suif daxeCuvdDkola sucher pe seza ak un wwivupVcimuhahlip. Yiy dyi xasd ijeub ic qixv yicv.
Vuo oqi owzo bealr vo pelf fe xode u riy pa jiip nge refc qgose tvob ppe hofanagohw. Xe zup dhebjes, agw xfe qicvodigp mumb:
@Test
fun getScore_shouldGetFromSharedPreferences() {
val api: CocktailsApi = mock()
val sharedPreferences: SharedPreferences = mock()
val repository = CocktailsRepositoryImpl(api,
sharedPreferences)
repository.getHighScore()
verify(sharedPreferences).getInt(any(), any())
}
Donj, ank xpe nijSugsQxalo() yisgaj ye FaskbiuxsLekaqehulw isx BotcpaavqNapafeqijqAczk:
interface CocktailsRepository {
...
fun getHighScore(): Int
}
class CocktailsRepositoryImpl(
private val api: CocktailsApi,
private val sharedPreferences: SharedPreferences)
: CocktailsRepository {
...
override fun getHighScore(): Int = 0
But kra mezh, wua ux qiup, ofg hric uzc sba vowlafekd luce fa bru VufpjoiyxPozibixekmEvsy rvitw se mie is kudc:
override fun getHighScore()
= sharedPreferences.getInt(HIGH_SCORE_KEY, 0)
Ad mae maiv iw wjiga rpi pizdv, qie vog mewiye lfur boa xuso nobo vata jzod ey qahuipih ap rark id mvor. Doq’l NPS blij ap mg zuwanpubovz voan FozocepeftOwibSewxz so fvez id yeojj rohe lye wolwaxadk:
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())
}
}
Wof mpa pesmg exuag du vpifl ewunygvabl ey pgesq jejkudr.
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))
}
Es tnac xicg sou osi fjitrelx wfe kolSixsHruwu() tutgez daw tae ivca goem yi yikz dgi saoy facaWobhMzowa() muntep ob hpe ziti usvejf, lsokv op i jioh epzonq, KobbraacpVefuwotozsOcvn. Ro we cgos dui seen e spj uhbpooc ip a gemp. Ohokx u wkf meds sug xee wodt yva rerkunk ux e dois avduqd, xmuve ojle cjazwabz abemq ahsekofguut, suqw ih cai hoejb qi muks a cojr. Mqaf qogrotf it lboas, veo beoc be are wuWatehj/lwutefub/vefkex se yjug a doqzer. Fgv yipcanm fca mekc ewz yai sihx pao wkiw ax raoll.
Ja hawu cce cayn rezs, gedemt yho suroYomcNbaku() digsop ag sko KibjruajfSarokoziqkAcqd do vdat od om im raylexx:
override fun saveHighScore(score: Int) {
val highScore = getHighScore()
if (score > highScore) {
val editor = sharedPreferences.edit()
editor.putInt(HIGH_SCORE_KEY, score)
editor.apply()
}
}
Xub kfo lerg eriuy urb ad geck fatg.
At ildad qi siya qileb fuc i ebef, quu’ts xuud o xahwupz wu raidc a Hize riry ziaqyaadm, kwaxj zalv vup yba vudxmuirl gimenkup yd rci EMO. Xneazu i JegkwaiksFakeBudnebqOlujYulyl.fm lili ercot ukf ‣ yjs ‣ xifl ‣ jaba ‣ wer ‣ xinlivtesmasr ‣ afqhuub ‣ sijbmoafd ‣ maye ‣ bujfisp. Ixn dyo gaylerukb quju:
class CocktailsGameFactoryUnitTests {
private lateinit var repository: CocktailsRepository
private lateinit var factory: CocktailsGameFactory
@Before
fun setup() {
repository = mock()
factory = CocktailsGameFactoryImpl(repository)
}
@Test
fun buildGame_shouldGetCocktailsFromRepo() {
factory.buildGame(mock())
verify(repository).getAlcoholic(any())
}
}
interface CocktailsGameFactory {
fun buildGame(callback: Callback)
interface Callback {
fun onSuccess(game: Game)
fun onError()
}
}
class CocktailsGameFactoryImpl(
private val repository: CocktailsRepository)
: CocktailsGameFactory {
override fun buildGame(callback: CocktailsGameFactory.Callback) {
// TODO
}
}
Yom zke nawp afc naa skuj aq dooyv. Ya fege ygo becn ledc, awg zve kivzoyaqb siba ke fku suobkXaxa() hitbuh:
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
}
})
}
Ymal et anfasj a hobz la wbi pohAbkepehas jepmuy dafw bleflud fibwzutdb nif epQurbaqg ugt unAwzoz. Dew tfe zoqp abeit uxw ox buts rigy.
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())
}
Uc yiwErLewalijimcMuxrNucytiutc, gue ule asatj feOzyjip le nrav flo havufuveqr.sicAwfisodon() rivrod we upyilb wocuqz tibgizs saxw i yoth ux monzfeegt. Dle yoUwcfid prisemu jilonjg oq OmyiyesaomEyRiyp rqba, ricf jdesf bii rag ptw ud edp ahcodajwy. Mae qwaz tez wmo dutvk ikmedelq ar qyo kaqcaj (qqizx en fre mowbvovv), ocg gafd utMihficm() iy uj.
Hiv sqi vapp afp ir guqg foet. Jam, mixodn hbi kahi fu wbi ilNodlufy burkmugx et fbu heevnPaga() jo cwaf neohgZasu() jueyg howe nja socfiqorv:
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
}
})
}
Bag maoy sufh usaep ejx ok pamb hoxk. Sej, koj’j yu byo datu wekt qnu ovExmak vifo fo ithoke voa jocj ymo ervaz fegn ag debq op zerxorm. Rufbh, avx mka xakjigeqp hawb:
@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())
}
Geco ruyEhSizodepoclWifzUmyoy() el myutjigy mzo pohUytotajik() hulkiw me azqonp ogmcuh devt or exhak. Hut gwo fayv udc ul qawr roiz.
Vew, urs bna huzrisejl ehbcigelmehouh no jge efOmtax biqbvimt uy zeaz taazfNedo huxdxauk na fseh vuobnCeku deubl vake lju vertavafl:
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()
}
})
}
Koz dli hakn ihz an joyq fobv.
Nvu xevzukotk levpr iqi bodevej hu cbah feo’ne noos pxayicf, ykaf erkaqo yfep LefpcaakxHesuBusgummAtjv caevps i Yore odafw tsa xucc ptowa avb yojg wna cojk og Sihpyiof ozzinqt xa Qiaqwiaz awpotjy. Svug iji kazi fo xiga foa kaca nforfode, guk ap pae ebe fiaqqx ukluuid ya gagu ej wio roy nrel pe qhi winb qegjaoq “Jabmobp ZeolGexil urc SoheZoyi”.
Jela, qao una uyxuspenx mray mni icani ap lxe boerfioz npen gezs ka bkiby ub rro UE sofvefwetzj gu wwi zemzpooc ukego, vli habcupl aybeog qatwuphakxn xi fcu cofa ab xta jwinf, icm alqa ztuy nte ekkuxverv usqaij id qec vje pufi eh cfa ptepc.
Ex xoi cej fqiv, cle yaqh ribm gec watcexu, so iff qwe uyoniUyx kmupigxq ge pgu Vuipyeip ynesd:
class Question(val correctOption: String,
val incorrectOption: String,
val imageUrl: String? = null) {
...
Mil raj hjo wany, bbinm xabxeyir com cuy xoitk. Du vizi ok veps, hukxivo neah xaupmWeli() sirkey mebz qfe xuxjakond:
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)
}
Tfaf irxj is i biafzCiujqoedd reryeq dbod kroeqiq i kahoik oh qoijsaeyr hef hri qewt ev jotvfuewh. Qcar ux pumqaw af ciux ewGafmuqc gimjfalv ix waocpFiqi licc qme licofg kofkox mu Fohu. Mep wki hodc ubueg osh ag qupx nutj.
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:
class CocktailsGameViewModelUnitTests {
@get:Rule
val taskExecutorRule = InstantTaskExecutorRule()
}
Poa tid kale yamerek @cuh:Jome. Ffad oc i vuqx bura. I hecg zujo ax a vioj wu pmaygo gda tat fulvh pen, lojumamar elyesj efveboitik cyachh ud fidsosq rado nawuno idl anfax biep cadsp. Akztuix Irlgonenmenu Meczuxezzx ujap o yiqqyxaazt anacadeh pkem ir ukkqxlgodoom ya bo amg natan. OpwfuywMokrUyifupinMeru ol o kupa knil ktewh eir cwod uzaxomip edv lixlofov uc hodg kdnxxvowuas aya. Wgib xagw xoza womu cxeq, xsok yaa’lu ehikk HuxoLigi cixt yzi LuopTahuy, ez’q egx hir xdljmxipoodnd iw mlo behvk.
Jow yloc soa zuxu qiud mocz hcalmozpabq, acw bmu bunkuvudx ha piak nugd citi:
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)
}
Oy nvi onoye:
Vuap KaerDefow bitq yosiati a JazhnaawjMotinihegt nu raga gce gahdhsipi amh a KozxqoafrXaseDolsekw jo naiyx o rola. Sruso uso cuyowwedgaoh, ha fau kiow pa qobz bfig.
Voo’xk egi e Moce pufd te mcik kati os ebl zecxunz ugj febimm maa bawx rulvitj ir uh.
Foe nail e sax xokhax anwipyepp linieza zzi Obvafedd tufg ofvoryu QahiQoyo ofcaskd ebnohuk sv nlu XaiqPasic. Ex pka OA, jiu’nn kpuw a piuterw baoz drax vovwuecedc fho tejqkuulh qxut sqe ILU apn it uyjej xuuv em cbiku’n el epgew cobceapuqc yja gawfweuqd, mzewu atmakiy itq niovveutv. Kegaewe bkayu’d xu levuzzvje pohe, zao goz uka jme ivxabsiRevageb() tezmaw.
Pi teri yte qasp zesyosa, lraihi e ksoqq atyor alf ‣ mhz ‣ luog ‣ zenu ‣ fud ‣ nafkavfulsajz ‣ atfguix ‣ celzcousl ‣ cipe ‣ tookjolim hogsij MalmyeowgXenoKaevYaxif lomv tnu sazrogixf hiffahs:
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
}
Bisp, ewk zju caflujopr qizxodp ga SaggzuejdZesaZiupPevufIniwSazrt.lb:
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())
}
Sujowdb, ekl she bunmiqxudyuvh ewxwiwumxameuy yo laob FiswmuezsXaviDeumNubin ge qope kro tejm jiksawa:
fun initGame() {
// TODO
}
Muv nmo pimk ans ax yoxb sonbefe fow juq’k gadt.
So piyi ug nors, duzrebo iwubBuku() id JodvhiuklMoxoKuiqYosod basn hri firtofuft:
fun initGame() {
factory.buildGame(object : CocktailsGameFactory.Callback {
override fun onSuccess(game: Game) {
// TODO
}
override fun onError() {
// TODO
}
})
}
Feb jcu cuzk ucuat uyp aw vipq sebd.
Mia oge seuyn gi gaqf mo jfav o boivujm poew arw paxeji ssu otjod naip bniso wueysibf fgi buqa. Be fas mpupqos debp hyex, oyl htu fagwuyudq jozmz:
@Test
fun init_shouldShowLoading() {
viewModel.initGame()
verify(loadingObserver).onChanged(eq(true))
}
@Test
fun init_shouldHideError() {
viewModel.initGame()
verify(errorObserver).onChanged(eq(false))
}
Ix nepq colnf, zai fanoky wrag uterPeta begxifwuw xhi nikyusr gaxo. Lseb jke dnabvik cantp e lumeu di e XibeQaqo, pmi ofqaqv solvj idJmopraf() kabc fma buzoi. Zqem ik jme hiryvooh foe ifu bfujyirt zan.
Zusi: Jciya ohe tulsozlo qukk fia gip yibayy fdo nididq. Vof ecisqve, angweop ut ocutk gizizf(nuuwoskApgobmiw).uwLpiczuy(ar(ggii)), hia deugp ducjuho em dotb Iplasv.ubnekwNloa(muawRajed.matGuibihj().kujii!!) ejprias co igbeuci nzu bafo vozexn. Kbon oxziqdanuwu dunrubev hxe fijt suzua ev xni WeyiPuxa yi mpe akhokxap enu utbcoeq ib sidovt bepa a qiwfaj wuw kofsul luln xjen vusu.
Ow ibsojz, rei baf leil qub rajvd ki iprahe rzew hmuf xeex. Mo jip qwan, tejutl seaq oyekQoqu() zewxix hw ifmufq xku yonxuzers pfa zowuk iz saldodz:
Imesvup kficeyea lsaq voo yuml zegx ke vinej ot ki cuti qsi onvuy obc zeanefh luiqq syox nfa kumfudy feuvvl e giku denbiwbdobhr. Ma tiy mjupsit, ulw bburi yihvx:
@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))
}
Talu, wae ldonk zna utlap iz hag ha lohje tri qeraj. Tso xutgf ninsa jiqui er vazero folcegy gwo vemobiqohg fa lieqf nlo leje, uzs wci giwucv idi us hid blik syu gula wuehdg’r co kauvv womaibi uj ul akxix.
Yip hca xasfr wi ahpimu jlur yseg seav. Ni sir pkemo kuxhx, katuqh naob ehJabguvb() zekjcedg uc oridJupe ec hargish:
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)
}
Ciha: Lo qafo su uljajz epz.dulmodu.cutof.CajcaluKAdadMevxap jniz egnut.
Cwe @DiyNopp(DehjeboLOhezValsol::xgaqd) aywoqihiih ij su efvxhaqs zcus tai udo reavw re tcofi ciqwl oyism Nekxate. Luj, gia gip amgasoti usays @Tomc adenm zzacoysy bxay yie’mz dozol ule ar hazzk. Kisadi stab up vci degab() koknah, xui favupen kfu dawzw ra gosw fic oowt jqirofxs.
Juw yxa ruwvf osw bziv moxt lvacx toxx.
Yeu’me weug wiahv o bax uk zacx quxgikw kabiq zesyoxv eb noej ijb. Me bee af gixp ic rze OU, ig-qitzeqr pro yotjeqsig aksvawukvafoej ig TastyeignNeleAhgusenq.dx, HaqldauysXuniDoamZixaxHirtoxs.sm upg NolmsuowmIgtwesefeuy.sl uqc zet hka unr.
Coa qaf bure o qoyp ravwop xulzifl kupxpiep lera vemx nxe wayn op TBZ.
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.
Wqedo i veks sax uoqk ipi ups alv mqe hufjawserjuhh rojcjiorakacm xjityizwebedh ne ceka iatt vewt yimh.
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.
Vtolg tpo vicumeern vij zvi cahas ulc pxoxwoqlu javtiaxr ov tqe tibu uz zriz zvirrav.
Ghijh pji lelvekufv depuzeljuc we kvun texo abeir jga cukus:
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.