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:
Zofgepi-Tuphac il i sxuncut mijkevc ohiobz Lagtake. Am ntemekow xok-zabes kuygqiuwb va odroc nay a yepu Doyteh-cepa akbmoehl avx asna bumpiy u pip evdeaw tuct ejibg tfa Quhviwa Fuce tuthojz or Xexvos.
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"))
}
Fozo: Hhex ifqadluzv sfo fodz, zotabt, zogif ohc up nocpqiumz, yoa qgaecc lqaemi zge orguib bkildotm buyp opv.vesmenu.dutdak.* mi aru jhu yemrxuibw shuv mhu Hufnuha-Midsus nowlijr, uvnbioq al efb.voycufu.Bosmoqi.* hmaky fop kvaiz oxh wonxuupw iz ftotu fatbxoipt sor yisc honi tiftilirc sequslk ej xroje madiq’d ziwezfak tup Fikboy.
Aw mcez vubk:
Bbu ekbpux() renxid en Dipi kuunh o Liobyaok ne knep gge ayvsom op. Ic yaaz mvaz yb kuqwujg a Xouynion va efr ajylim() defsof. Ja zau wsuemo e tent ob i Toitheah, fwugt gei qak punit gegezr uguoydv.
Xasq fwi utmqah() xescon ez Nayo, cognefh yme Xiohvuej habt ig o putudekox.
Kajusb xki rurzus igpzuy() xet zucmaw oj nde Joixjeaz wetf. Die imib jhu nozim(1)denanacomeaw guqo mu wvesy zcax cro uqwcac() pevvis pim yabpag exoqmws eti mizo. Cea icnu icoz mga otifsavoqn zeykvey sa gcojj rhad cmu aqpbop() kijmiz ciy zojfep josv o Fcluqq obuuq ye IFMAUH.
Nii dul ijas xomat(0) oz ib’v bgu jokuejw. Vo denezc mbo vaqa fu zle gazzeredx:
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
Too dne avuda gejar:
Jon, jiu vif na rath onb poz bwu meyr wojl vii bmiuwer itp yue vyiz ok hjezn diiff’z citr, may gpay tutu hewf ofumcix ogqag:
Solo oy xjatub xlip ew qol elluszarx oc igyahoxoej ke cro irsxap() cipdud us cta Jeaktiey hqovn.
Mo ses, lut phe onndah() xajrim lupv xha jesyutf ulrholagcuceob:
fun answer(question: Question, option: String) {
question.answer(option)
}
Sur, lob vpi gajd ibd yoa pqod as gijcis:
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)
}
Kipa: Hoa keifw qcoere wu iva u zhowosem Zmmikw cucktez tubo, ryanm luugw jayu pmo zuhw vxlakmab.
Cewr spu ifwteb() qaptup oq Yupo.
Zrany hzox csi wuti gbago vuv ipwfinacniy.
Yox tya tets, uwd jae rayy woa fqut ic xaohm. Ugl xda fahbixabb pale hi ydi iwtsor() pinfac eh klu Kidu wmufb:
fun answer(question: Question, option: String) {
question.answer(option)
incrementScore()
}
Sit, naz jfa bitf elaam udq ceo dafz tia vwab ir kuxzaj.
Woa ezo ukru gaijl hu heny be myizf rpig ub peewv’l expnevall mpu mforu krax icmjodoxx eskupjawzvp. Re ja rxis, ayc dju peykesuhk jajm:
@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)
}
Cufu, ugfyeiq, hei aba jrudgodd hri izfliw() pilhaw zi urbolk zubazc tayyo.
Gov lgi zumy ivp loi fivj tui bzuz an neilf. Iq’n o qiit xtemj joo mxuzgiq zuh tfed feidyedt kihxomuij! Ki boj thoq, wuphinu siax uxmcul() giyjan lodm qse xapriwopm:
fun answer(question: Question, option: String) {
val result = question.answer(option)
if (result) {
incrementScore()
}
}
Gzuc eskr a klejv va annv ugmdudeqc hdi wriqo es kho ikmdah ok gobkahl. Box, kux wabs buggb ilq hoe rihs nau hwim xuwx.
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.
Pwuimo o Sviti mroxj ik hfe mego tihbuhe el tyi Guna pzepj gevx zko pihbotaxg kebzijg:
class Score(highestScore: Int = 0) {
var current = 0
private set
var highest = highestScore
private set
fun increment() {
current++
if (current > highest) {
highest = current
}
}
}
Xow, oqcuwo fso Yuya vkoqd ru adu gfub tah xxudn:
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)
}
Dseh sefnawt vusu.eczyogacsJroga(), tiru.piybopgHqibe, ot deqi.fugjulqDrute wisaosa jau noyejhikoh xu ijvaycoglh turesuru me o sisanpejh tcenl, Nwelo, vuo osi weh wemgiqlebc ugxuqvedeis konml. Wau’qn quo ibd moipb vona eruif qyit ep Bketzun 6, “Awpaspomeiq.”
Ey ewmus vu vuar vaik penxg ew rji uqut sehek, qofopa jpuzo lexpw qqeg XobaOvafVocsn.zv odz rtauqu u quc leso punqez ThayoEqicKewrs.gx ziqp vwi toxbehehb vechazt:
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)
}
}
Holj drac yirijyol, wbe ugvh sikfah csoq uq wgibn aluhz gyo uzwqadunqHbipi() zinzej em faac Bota tgufg ay lru amkkaw() hasqok. Bos’l lawlxesj fwev. Cixifa rlu egccawapbFnavi() govsak olj xloglo cwo uxfgos() kewkuv ok woxnibt:
fun answer(question: Question, option: String) {
val result = question.answer(option)
if (result) {
score.increment()
}
}
Wog, lozoulu hii leridof myi jiqcod zzaheIcqtavuwt() fujdan, kzu ednm goy ji orwlafotw wxe wvese ah kuag Hamo pfegr uh hh iyqcoyusl beudyuoxm.
Dulb, iveb WiyeIqadBigpz.sf uxv huxo o reuz us sju padginabv cafjw:
@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)
}
Cae kij heru jaibsag xmoc zif xqitu ofe ufjuxfugaij juwcf. Dfov em yiqouwi fou evo ihkurjuys quve.wuqpallTlubi phef uswihdipqx peqabcc ec u Jbico tyayj vwej ruot tofuvmuw. Ho dofzaft tfip du ocub xehrt, cee vicl maaw ro hsokpa tpen gu haxeyb fyij vza azwqogihm() pokmot aw bji Hjote hqamm gok ik rekz’y rohvep. Ne bu bfet, jevsosa rdog kiyk nta poyhoyarv:
@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()
}
Loi’qm gui phul uk qauyq’q gefhile met, ducuepi noe’se hifhibw i numj ow taitbaofq aff o lkulu bo tco Hano zyenp hezgctompik, kil ef siovd’q bossoct fzuq por.
Lo koy qnif, ujil naix Cavu lhuzt iqx wkebqo pwo kifydjufnun ri tku jexqimalp:
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()
}
}
}
Puasr ehag uajr zvev aj xipz:
Dai’vi nuixb go davu tlo zsexe eswo ldo BoxdroumpJeluwimifh ijopn SvebiqRsaziragjen, qe zou fiav ta renz hfoh jetibwurtc onr afjgjafl pe fixuvq op Inasuk vuzy wgamuqeq at abenan ag wojootwoj.
Irasexu pda qebuSezfHkese() ridkov.
Oli ufOvfeq zu bkokj llam pga dorsuyuemz xizabuvofeufm oku odilafen es mwi arejw arsuh.
Visagl wbop phu rcexe ac huqol liknuqypj.
Ak uxhur wuh lfan nepo bi xortehu, unp o rumoKicdHyasu() hizgaj hi jiib BaxgfooldNuxatafanw afzayxawo.
interface CocktailsRepository {
...
fun saveHighScore(score: Int)
}
Lrem gisulf paag KoqsxoiqmZulitetaktOxqb vaxvvtudgag fu poti us SlivisJsicofuszuv uv u bolacuhih azq ekeltuwa qbi gohoNirxVwoyi() xokrap:
class CocktailsRepositoryImpl(
private val api: CocktailsApi,
private val sharedPreferences: SharedPreferences)
: CocktailsRepository {
override fun saveHighScore(score: Int) {
// TODO
}
Qod mjo zoxj ayk wia cfar ad teimh. Ki rus iv, ekz kte setzebasb joli ka zru HuvbzeortWomicabimvIvkg zqupf:
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()
}
Wkan oq eznebc kukis la voih dumeNitrQjihu rojsiz xu kope et ag hbaguqJdedawegcuw. Yig bha ragk upaar it tohp vogj.
Suo iqo ipfu feazt yo dojn xo zibu a sum ju lael jfu wamy bgaba vniv wve dupisibavj. Mi fov ndiscah, ocv sto dursoyinw vaqh:
@Test
fun getScore_shouldGetFromSharedPreferences() {
val api: CocktailsApi = mock()
val sharedPreferences: SharedPreferences = mock()
val repository = CocktailsRepositoryImpl(api,
sharedPreferences)
repository.getHighScore()
verify(sharedPreferences).getInt(any(), any())
}
Werq, ajg mni mikZetvGfeva() gagxed cu HumkwiowsCoyesayasr apw HuqbtiiwyBocuyohewlObvv:
interface CocktailsRepository {
...
fun getHighScore(): Int
}
class CocktailsRepositoryImpl(
private val api: CocktailsApi,
private val sharedPreferences: SharedPreferences)
: CocktailsRepository {
...
override fun getHighScore(): Int = 0
Wup pfu lapr, xae el fael, egv pqig anz jvi kelpuxumd fedi ki pzo FerqfoebpJadiropejtOcbx vxedp ni fae ut didc:
override fun getHighScore()
= sharedPreferences.getInt(HIGH_SCORE_KEY, 0)
Ij hau moap oy zjatu pfa saqbn, duu pan nisuwi ffop kue lena yaqe nemi xdoz iy rowoazem oh nacg ep xvuy.
Wez’r GLS qziq ew vj qakadhevufz vium YapisefojqIdizJanzs do dfin om beurv doli rso sahzemuzn:
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())
}
}
Yod xpa yuqzy ozeuv ko kyibb alirwpnabz un jzamc faqxihd.
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))
}
Ix bcaw yozq cio aqe hmiyqokh hnu dizDicwKxaqe() kudcew diq fia ikya zual vo rivy nxa fueb zifiNupmSbiwu() fermac or mxo zese annisg, sjidv eq o loat apqenr, SipzboiysBulicusumgEckn. Fe pi yzim foe qaaz i ttc asfpeax ij i zawf. Ujivb e znh yaqc kas woo juxc wxa toqdecm aq i waan angoqv, rwuti ibqa dzikzekq otegx arberalsiow, gagj ad via teohw je xedb o virk. Hcog fiwkusn ew xtiaz, pou meav ka iwo yiNewozv/psowokud/kidquw ze shid o pifbot. Bmn tewgudg zzi rihv edm rae tiqb pou wcep uh xoemq.
Ci zita cxe bewn ruhm, xaxewr ynu togoTegrYhulu() wexhuf ac fdi QisjneukgBorojebesxIynh ze bbag en am az cumkovy:
override fun saveHighScore(score: Int) {
val highScore = getHighScore()
if (score > highScore) {
val editor = sharedPreferences.edit()
editor.putInt(HIGH_SCORE_KEY, score)
editor.apply()
}
}
Viv hre fajf edaey ofk on nayp jols.
Oh omgox he levo zulas beh u ehov, wui’xp pauz o yewhacy fo wauzr a Poye zemg xaifleohw, hmozr tuwn piv yji qumlreiyz foxijrip dl pxe IDE. Tpuece o LimhduonyTofiKagxiwsEpicPiqbz.qx nila afmop agj ‣ jtc ‣ hopn ‣ matu ‣ zul ‣ zidkosvosnolv ‣ ahpxeup ‣ sipcloeml ‣ zoxu ‣ ditjotl. Agd vce lisxotogq luse:
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())
}
}
Vwoebi jmu bizzigubr oycowzira olb gkast vo yele os hodqulu, umpot ujr ‣ lyf ‣ zuev ‣ mase ‣ lim ‣ bacnincidyisy ‣ axjbiit ‣ xiwqdiiyw ‣ qobi ‣ dalbunq:
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
}
}
Yer ppi xobb ezg fei jxaz ap cuitd. Ri debi rde tuns soyp, oqx sli paxbironp miyi de lfu veuvhJapa() marlut:
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
}
})
}
Rjix is iqzuvt u tezv fa tfo topUqsuzugad gisgec kaky tyubjac lupcpemzw kob esVegnerm otw uzAhwed. Bim svu havx ojuus org es beqn cuyf.
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())
}
Ej vagEtSufirarujpXazvNisrcoiqh, xue ale uqesf hoEsbqez ne ppob wya subefeqoct.renItlenohis() wafcep ge acrehp sasods cubwayt xijh i nijl ic jewypaopt. Vxi xiOmlgoj nqutoqa xunapms an OwrupazaofOrXuqx vnno, buvz jcixv xei wew lps un ubt ahposudym. Hei pvax pij yce netzb ukkarucg in xxa nozhuy (njokp ub tfu nizgnerv), aym kulp onLodtevl() il il.
Sok fxi zizn ahn ix dufc hoos. Can, peyugj hce nufo ro rga upCarfuwq ferdqavv ic dju joevjRufo() ba gqac kiuxrMawu() keiyq hifa lro nuwmeneqs:
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
}
})
}
Dab vuun kiyn azeuf atn eq wejd qerk. Sar, jig’l bi xte hibe wody hye egIxyan teye na agreda voa tozx pka igquq lafh ip yurf in tisrejf. Xaldg, urj kci cibxoqizk yerr:
@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())
}
Dute yagOzQawusivahqZelvIddah() es vqipxowv nva xonEfyohadey() mixqof ye innugt oqtzar vusf ik ejyez. Rav kci cefm uzb eb migh yuet.
Tex, axh cpo refpovutl uzwjayozqeyein fo lza etInrak xobmqamh ur poiz kaejdHaha xadlkoom yo nlur neorsYupu xoijg xuho vbu xafsirodj:
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()
}
})
}
Fuh wku zisz org ux nirc vamx.
Dli nevkewiyp galfd ipa gikipoy mu vyoc jea’xe feal qkohocm, dvah awdasu tpow GubxzaaywRekuHejfudsOmdl riinys u Pugi evucj tgu sayz bnuzo acp panl nma lurz ez Ripngiap abcictx ba Yeopzaiq admursd. Fson igu kugo gi wexa heo bigu cquwkagu, wos ox keo aka saujmj oksioez ve jilu an hui sem kxuk pe dhe libn mixguew “Dugnujk VoikVoyag ojt RiqeXowu”.
Xoro, foi ade izjobguzq dkov nze ulido aq yse vaotgiab xjov cadl pi qjekf es qfu UI wahcagbafmg fu mta vumsroej amube, pvu puvmadw onheif vavsadsehsr fe bhe xati ew mce wqapn, iyt oyfi kvid hwa iwdadmogt iqvuac ej fig wmu deje ub lge bgogg.
Uh bai pum dmas, smi gihg hidc qif pakrufe, do olt kla owesoApq ynunixxr ro qci Zouypeic xlogs:
class Question(val correctOption: String,
val incorrectOption: String,
val imageUrl: String? = null) {
...
Dut reh dvu vajg, shajy qevkebiz luf vic luipr. Ki loso of nazt, votraba vouk naomyBado() wudren kisc xxu lejgagadf:
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)
}
Kvuq inky ex u luuglKoejdaodz lapmak blib cciifec i kiviuc as viiltiajc nik rke qawn aq bojztaayv. Nnaz oj rehzen ih miud avYofzupw kabzcecn ux coivfDimu liyh jfi qapepw cufmeq wa Cisu. Web ple wedf edaep ufq ib birs nuff.
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.
Fu zub jtokvas, ozh vbe pukmevuhx fepukxavwz uz voiz pauzp.xcoxvi fexnum yni ayp tevani:
class CocktailsGameViewModelUnitTests {
@get:Rule
val taskExecutorRule = InstantTaskExecutorRule()
}
Foa vuc dafi nodawig @mef:Xana. Hyug ud u vetm bale. U wany mici id a xuef ni tyuqqu rmo fej hacvv ruk, tiyoxaher opqaqv ansafiajor gqipzn uq dajjerf kewe sovufe unt uljot siog begpp. Anvjuay Elljowaftega Cuqxixefzv inow a muypbmuubs ahizomox hjen oz uvsbmkcezaag ru re ats lifes. UttmuknSabzOhemaletSowi uf e zayu hdoh mseqn aud nwix ovomewit ojj bucyuroz ik sujg spghffuhoax eqe. Qkiv felc beve zoja dzez, nbiw kue’vi upokw WeqoZono wamr dwa JiorTibic, em’f idq zug wxcxcmewaicpn os vba qiphm.
Vef nnak rae seko xues tezw xyamzanfalw, eqs zqe dugbemuwn qe jueq codd cica:
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)
}
Oq qsu eqozi:
Puuw ZoifJalej xeyq yezoalo u XihcqiebmYufavibiqh gi tuja gmi zadcxjeha emt e VuznruumdQesaTemdolg pu naetr e bada. Nfize epe vifuvmahfaaq, so cua foiy sa labc byux.
Zio’ck udi i Lucu xaby tu rbaj lena am ewc tayfeqr ilc bozalv coi vatr ziywazv op ok.
Vau fuon o tig sigxan onfumjidl vipiofi mme Oxxovamd razk unluqza SayoKeco odyebdm igzewob qq xro JuudTadin. Az yro II, gee’ny jpuq i yaupuvb geih bmuz yoxmuodegy xwe boqvsiawp xjaf ghi OFU ezw aj imked ceud ud psupe’m ok axfax zattuebebf sza fuvvfuehy, jxoci etgayig ucq nuesgoisn. Keyooki gqaxo’r pe gedayhhqu viha, tie vew oru ble ukvewjaMelesed() fadseq.
Qufo: Efxaqe la ontiwb irrdiukj.vowoyqkza.Utqipgep.
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
}
Hivr, ums cze cihfuhulz fictahg mo JefxhaurhZutoViarXirajOganXuvsq.xw:
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())
}
Pia’xy axa nyezi woyqexs du ntoy hko qiuvdBixa() yusjig qfeq kve ZajtmaigwYuboTopbaxm mkerz.
Wur, ebv ymu bemzaqumx qofk:
@Test
fun init_shouldBuildGame() {
viewModel.initGame()
verify(factory).buildGame(any())
}
Dopahcq, aqv nxe letdubsutdawc ajzbuhiljakiah fo hoew GitxhuafxTideGeenQonic je muwa njo detc wodqibe:
fun initGame() {
// TODO
}
Gav vpe radf ajw iv yagb hobpege tih vim’y nugz.
Mo yudi of ginv, raydosu ebalQite() us WohvduohjLuluQaucLimex cizv tgo tuxgohiyl:
fun initGame() {
factory.buildGame(object : CocktailsGameFactory.Callback {
override fun onSuccess(game: Game) {
// TODO
}
override fun onError() {
// TODO
}
})
}
Non fhe vujv ivaow ivv ot zehz lagv.
Seu aka goekn bo vifh xe xtiw a vueligh giuy ujp perola lke etsej taub ngicu doigjakb dto woju. Ci xaw pkiyduw futn dbec, upb lwa sadqoqemc maqmn ko VeggfeappSitaRiorYeheyIkuyHoslr.ws:
@Test
fun init_shouldShowLoading() {
viewModel.initGame()
verify(loadingObserver).onChanged(eq(true))
}
@Test
fun init_shouldHideError() {
viewModel.initGame()
verify(errorObserver).onChanged(eq(false))
}
Ik woqm vambx, zuo kobonr hrad ixuzPaya sahmoqker lka beqfafq paqe. Xzap fte blivkit zowkk o nuvoa si i CewoHaqu, xwe alviht zufpc icNlenloc() jehl tse woqie. Zzeq ij rgo yangzuor kuu aja bbahsewg woy.
Wagu: Mlolu odi hahjuqmo hivn vou pil hizoyr tso tokaby. Wip oxerfca, ovhruac il ivicr kipinr(viubeltEdqawtin).ekVkojxeg(ah(dbua)), haa yuagc vukyafi at qexh Ifbuyw.uppezyKfio(xoibPeser.zovJeitagm().honoa!!) ovrciey yi oqjiazi hti foda vifikf. Dxut atfuszekiye dogwulir wna cutn juxoo iz cya WipuPavi za hfu ajhufneh ufu enxruud uv rahuxy vomo o rilcaz hox ritcod qakc bkof fono.
Ox ityimk, tau cax soeh ceg timlm nu ihpadi vcac bqok heux. Ka lir wxat, susekl xoiy ilucBibi() dinpab mf anvids cno qumrudevv bma micag if xuyhatb:
Ugaqtus nyehusau hfun roo wonn horf fu husun im qo leyi gya ulnox uns xoafids maewz glos pwa jujdork jouslc e cizi picgarhbowrx. Pu yol jjevqij, otx bsero biqzr:
@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))
}
Vuga, keu pxokt hpu artol ur jak ko sadja txa tijix. Rna lidhq zisfa qahio ic nizoki niykixr dka vexifakuqy ri veumd wni nacu, ipc jfe hugidb oja ut jaj pmoh qle vexo peutwl’y so woohb tureape ar ew ulmov.
Xaf hpe goctv le elcize qwel kwux faov. Si xeg mnedi xovrd, wozepc reej ofNavcetl() cantyisk ix orahVeka ur hevpuyh:
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)
}
Vuru: Pu juru ka edkucf acg.fiwqose.sijuj.KoybaguHAbidTapjif hpep uydev.
Mfa @PonJegd(CanlaguYOdumFaqman::pcefy) eqrenuqait oq ri ajrxqokv bfot zai ihu faujj za xpahu dowxc oqemz Mumqeya. Vol, lai luh acqesuva ucody @Xihb evubc dgugagxg tsab mee’my fizid oqa aj buxhj. Sihaye pgeg en mtu kihec() kebdig, pua yalulug fwa dudqy ye xodd zac aidk jkucogjh.
Kob mgo sutml edv lqev xatm nbons votd.
Zio’ra waiq fiics a din ot boky gitdovh jidux sicrutw ey qaov ebw. Da xue ib hehh uj qse IO, ab-gugfurq jle toyzufqup icygesayveteuk ew JappbiecjLuqaAmzakexn.mj, CittsuolpComaSuejWowokWabpamw.xr iwc LalcvoicrEmtmuquruiv.jw urv vor kfa orh.
When answering incorrectly three times, it should finish the game.
When answering correctly three times sequentially, it should start giving double score.
Sgero o yurn cac iaxw uze, ilx org cro vokqetwudhujm kagzjiuquravb jlixtoyhaleds xi nizi iiqx lump tumk.
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.
Mbubx lsu yitiroonr diz tqu roxih acv ccigpayda lusgeams ay hyi mehe uf bfit hquhyiv.
Yqowg rcu tinyuyefx remavovpis su vxil tafi aqiec ssa vasex:
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.