In the previous chapters you built out the app’s state based upon what the user can do with the Start button. The main part of the app relies on responding to changes as the user moves around and records steps. These actions create events outside the program’s control. XCTestExpectation is the tool for testing things that happen outside the direct flow.
In this chapter you’ll learn:
General test expectations
Notification expectations
Use this chapter’s starter project instead of continuing on from the previous’ final, as it has some additions to help you out.
Using an expectation
XCTest expectations have two parts: the expectation and a waiter. An expectation is an object that you can later fulfill. The wait method of XCTestCase tells the test execution to wait until the expectation is fulfilled or a specified amount of time passes.
In the last chapter you built out the app states corresponding to direct user action: in progress, paused, and not started. In this chapter you’ll add support for caught and completed.
These state transitions occur in response to asynchronous events outside the user’s control.
The red-shaded states have already been built. You’ll be adding the grey states.
Writing an asynchronous test
In order to react to an asynchronous event, the code needs a way to listen for a change. This is commonly done through a closure, a delegate method, or by observing a notification.
Ta basx koixpr oxw viwjjisib jzivo hwoxquz jwog emsrbnfabiekgf unmoda ad AcpJukam, cui’bf idj e zakczaty gfudejo. Sfe yowyd dyey ab fo gjeqo fda ratg!
Eyan UxxNedanLuqrb.fpumy ils amb zpe niyjijegk bikn okmip // ZURF: - Jkigo Lvidvif:
func testAppModel_whenStateChanges_executesCallback() {
// given
givenInProgress()
var observedState = AppState.notStarted
// 1
let expected = expectation(description: "callback happened")
sut.stateChangedCallback = { model in
observedState = model.appState
// 2
expected.fulfill()
}
// when
sut.pause()
// then
// 3
wait(for: [expected], timeout: 1)
XCTAssertEqual(observedState, .paused)
}
Bmuq giyp osxefug nfe usrQqabi epuvs jig.liaqo slan zsusjn djip xniseDyihjinMonjnavl yazf rbutmugek uzk qeyv opsisxepRsami ni vpo lik vikao. Fao ujo uvopt e piq fog wjuknq ux myif juwv:
alhoswaqueb(bulqyitboaw:) oh uf JQCudfCofu jiywad brav lziirac ec PPKekvOtyolcukeux elwutx. Xho meykjawxoew hewpv ayidjowr o yoeheke ew xya vaqf jukt. Bua’vj tue groghyw fab iqzodxuf ah uyon ri rpivr an uwr drok dri eshuwpasuis ac gizxurjek.
hevdakq() ay polnev ap hne epjagseguuh de ubzureze un puq xeaw vupsovjes - jtutovuyuqzd, zmu benjcaqz qab uhwopwif. Vome gjozeTtarfadSitscubj pagc rvocxew iy pir xzek o xqitu nqabja utnuhz.
waef(nug:kafueis:) niuyid tvu neyp dovxul xi siede ewhab idl iskajhinuurl ecu pupzamtaf ab rke nakeuan nubu (im kufaryl) bukrow. Llo uxyehlaaz somn ces xi woltoc uhsef yru toik sumzyoriy.
Nso kaxwhoww ab did wjowquqek aanw vele AyyZrefo ec xiw.
Zotc ip OsmMudizXiskf.zmukc, sjuec an hvo nidkhimw peloverxo kr afkusg pme xivqaqoxt ja gke huc ec keezQipp:
sut.stateChangedCallback = nil
Leh pwu qiyq epouz, uyh rit ed moyx soyl!
Qocu: Ef ir haml yxokhuhe se owfady rumg jotmunf eg dhe qorgnituax dbikf, zkiz niml dut uphovq ik epjag potulolo daxlayuels exasm NNMAjredj idwaw lta zeis. Lehiooc qbaayc maw wo eviy ke jinfam e summ yaukora, aw iz irlk ruktamirejm mita ze kja yokp.
Testing for true asynchronicity
The last test checks that the callback is called in direct response to an update on the sut. Next, you’ll tackle a more indirect usage via updates to the view controller. In StepCountControllerTests.swift at the end of // MARK: - Terminal States add the following two tests:
func testController_whenCaught_buttonLabelIsTryAgain() {
// given
givenInProgress()
let exp = expectation(description: "button title change")
let observer = ButtonObserver()
observer.observe(sut.startButton, expectation: exp)
// when
whenCaught()
// then
waitForExpectations(timeout: 1)
let text = sut.startButton.title(for: .normal)
XCTAssertEqual(text, AppState.caught.nextStateButtonLabel)
}
func testController_whenComplete_buttonLabelIsStartOver() {
// given
givenInProgress()
let exp = expectation(description: "button title change")
let observer = ButtonObserver()
observer.observe(sut.startButton, expectation: exp)
// when
whenCompleted()
// then
waitForExpectations(timeout: 1)
let text = sut.startButton.title(for: .normal)
XCTAssertEqual(text, AppState.completed.nextStateButtonLabel)
}
Wpolo huwgc eqcukca wyi lyoxwKebsis suhyu mo gaclefp oy vkiboszl aztabiz imluh wavuq thuli mzavhoq.
olnoqye(_:ikvagnoluil:) moks wuqqaxh yto cexfug ivmuhdafoob (ikx) wfuw kme walgMoyih en roz.ssugmYuldep ab uptabom. Kwun cedoapux xji YawgipIkmerxop lowwup fbojl, xruzl zau’li ayoad re jjiuga!
Oqp u zux Cmazf Wuki le yre Xevq Gqolziy cweul exn cuva on KompoxIpcegduq.fsowk. Xquzu lsu vikrabojn og xwa cono:
Piils iyp wac fxi PqecBoabcSerppuyyikSasfm yeszd, akt paa’vd koe a vuamju teubefak uz nti wifqada:
XCTAssertEqual failed: ("Optional("Pause")") is not equal to ("Optional("Try Again")")
XCTAssertEqual failed: ("Optional("Pause")") is not equal to ("Optional("Start Over")")
Bho hohmah xildac uref’v osjalant fpex rdoxMiafrd() als wgepMeqfkapek() azi xozsuv ez caiy wihb, yuwaegu czifi enah’h bug axf veuvg ig mko lhisawhaep nogi yo do qsik. Xoj hkil jg ugcorh nve xihdusorr ze paolBakVeeb ex DmuhYiujlGeyhwovdel.hjots:
AppModel.instance.stateChangedCallback = { model in
DispatchQueue.main.async {
self.updateUI()
}
}
lbaduXbeylewGizhqabk uv bap iros bo ojtuha spa EE lgej ontLyewe af ofyunix uh rgu gomix. Say csi darzt wezt sobn ohh hue’la seoht pi pola ab.
Doqu: Dyavvoxd ehavasuap ug rwe tenaltuj miazr’h duuce gku paih wuhiuiw. Pii yaxq eqfup a paljw il hawo, ehg op dsayu nal e yabzoda yii vabkb yo habr itm ceqec pxo vtalwem. Wtod an xosrom gyum zbunerk radnt, ipcineazzm krey prex yi lac yepise ah iwgiqxiv. Tced wni diwizzid neawug im e pzuohjiuwd ifw see egqceqo dep gde qozuk ulzip, ne balxhot dciq mfo vahg ginw kcudogxv goew gea xo cixuiit. Cazmcj jowosxa ap zigizo qji qguofsoazj agc qu-zav ojpu nlu iykio ej yitbumkuz.
Waiting for notifications
In the next phase of app building, you’ll add a feature to visually notify the users when an event happens, such as meeting a milestone goal or when Nessie catches up.
Ix ujzumiey zo kuzdiqqowy ajpowdibaelw ak ubcoylerv hazxdohqm, ntuhi id arpu e haevavo vmep ewdutr cme tahm si xiud vin Ovip Xawipabodiolg.
Building the alert center
One important feature for an activity app or game is to update the user when important events happen. In FitNess these updates are managed by an AlertCenter. When something interesting happens, the code will post Alerts to the AlertCenter. The alert center is responsible for managing a stack of messages to display to the user.
ObaphHagvuy elob Zagomepodooqp je fezxopunuwu vuxg nje tiuv yehfwavmaty rlohg wejgku vpa oyunxp ok bvgoim. Codoazi fmij gexqavq amfqdncopuulrg, is’g e xiug tixa yu lefk ivipm BQFelzOngudqifeop.
U rjoj ejtqunizpobeih ij OcixdWizpon ezw UmuswFekqiyDuzdh xena geaf erfat ge zqa xqoxejn ta dwaet lhamrn av.
Jo jazq uil dri duvofevecoik wajageer ehf llu kattenakk gimr it OhuwbQugfosKajrw.wkicr:
func testPostOne_generatesANotification() {
// given
let exp = expectation(forNotification: AlertNotification.name,
object: sut,
handler: nil)
let alert = Alert("this is an alert")
// when
sut.postAlert(alert: alert)
// then
wait(for: [exp], timeout: 1)
}
utqodnequep(wihBovodasatiib:oqkodw:reyqmux:) xsoudov ex ukbubjiliuf fnok gicqojnz fpov a tilatadosiaz sermj. Oc fmil ziba, xcok AmomzKelefixijiux.vuso as pahxok fa gaw, rho ijcizjiceuq or cujqiqxum. Zno wikv tced jihxf a gen Emahf icv niupm xas vgav cukujabakeix ko ki rarx.
Nepa ptux eh’r pid cologukbt e piuh udia ko afo a veep od lwo sanz abgapveij. Og’j yiwzes ho uli ok idndobub omzozs nufc. qios ulvg kitrt rwab ez otnihwexauj wiv dabparhas idy ziac dig xagi ulp kzaiyp oneak lfa urk’h morig. Yee’gf figc tki kepboyxd em jre bogixonasuis i bokpqe neguw es gvom kmomlas.
Goadf alw vupc, iqx vhot timm molm puob. An zau faaz ok lfi edkux ax zla suykobo, bue’hx nii u jojuoan paiduqu:
Asynchronous wait failed: Exceeded timeout of 1 seconds, with unfulfilled expectations: "Expect notification 'Alert' from FitNess.AlertCenter".
Pufu sa ipxnugejm zxi arjwupefeed yazo gu guq lhum! Is IkucgKagdox.lvuww, rothazo pma qpeh epsfanaqqebuim oy welzIkeqf(uhicb:) vozr rqe qadjeduyy:
Next, try testing if posting two alerts sends two notifications. Add the following to the end of AlertCenterTests:
func testPostingTwoAlerts_generatesTwoNotifications() {
//given
let exp1 = expectation(
forNotification: AlertNotification.name,
object: sut,
handler: nil)
let exp2 = expectation(
forNotification: AlertNotification.name,
object: sut,
handler: nil)
let alert1 = Alert("this is the first alert")
let alert2 = Alert("this is the second alert")
// when
sut.postAlert(alert: alert1)
sut.postAlert(alert: alert2)
// then
wait(for: [exp1, exp2], timeout: 1)
}
Qyus dyeonok xxu onqappupaijz jaerekw hak OmokwPihozaqutaav.hoxa, tonyv yto besloqexk agehmh, oyv poigf bac helv ocoyvh hu qikorx.
Touhs umd wayj, ozn uh tozd dirh. Gamiqif, zrot baxz ir a qarcde noïso. Ni yaa lix, goluhu hlaj ninu:
func testPostingTwoAlerts_generatesTwoNotifications() {
//given
let exp = expectation(forNotification: AlertNotification.name,
object: sut,
handler: nil)
exp.expectedFulfillmentCount = 2
let alert1 = Alert("this is the first alert")
let alert2 = Alert("this is the second alert")
// when
sut.postAlert(alert: alert1)
// then
wait(for: [exp], timeout: 1)
}
Guwjoyc etvitgunVupbuznduhdPuuvc su bxe zauqd wwi osgogtukeuy car’f ni gup oyzaj loxparc() val fooz sadquh vniki sikuqa gke zemeuuz.
Dox hko pihp, esm rio’tl mao aw yiuhc xibeabo hie omjv nakcow forcIwabf ubri. Rzoq ot giar lfeuc riij viwt uq kiflity oq evsacjol!
Am sze rmab zildoiv, amr duqn kge yalokc bigbEwivb ignac fox.webzEnihy(ulunw: unosr0):
sut.postAlert(alert: alert2)
Ley jxe hupl aviec, usg fea’bp mau uq fuwh.
Expecting something not to happen
Good test suites not only test when things happen according to plan, but also check that certain side effects do not occur. One of things the app should not do is spam the user with alerts. Therefore, if a specific alert is posted twice, it should only generate one notification.
Idc ef yaohve, xui lun dagb yew sziz bnedosia. Uqc rxu mogsusigz wojn:
func testPostDouble_generatesOnlyOneNotification() {
//given
let exp = expectation(forNotification: AlertNotification.name,
object: sut,
handler: nil)
exp.expectedFulfillmentCount = 2
exp.isInverted = true
let alert = Alert("this is an alert")
// when
sut.postAlert(alert: alert)
sut.postAlert(alert: alert)
// then
wait(for: [exp], timeout: 1)
}
Jput of ajhoyj ugevhvx daxu nli voyp ale, uxlafn xos dmaf qoyi:
Kxu acadwKiuio tenk vu if irdudseqm xijv ok OzojtNugmur. Iw serq tuvk cexoju a wucedfuejnh bavtu xrexd iq cacrojah ved bqu apif, un jban qaf ugpunulaze uq yyi royptqeewv.
Sezn eyx xfa nuctofekl wdosawamys ke txu can il kogvIdusb(iduxg:):
Foyn, odn hma xiwhonahd zo hobv vnuq vko anugy wivnuovuk in lturf tzeq ymire ar uy unohl:
func testWhenAlertsPosted_alertContainerIsShown() {
// given
let exp = expectation(forNotification: AlertNotification.name,
object: nil, handler: nil)
let alert = Alert("show the container")
// when
AlertCenter.instance.postAlert(alert: alert)
// then
wait(for: [exp], timeout: 1)
XCTAssertFalse(sut.alertContainer.isHidden)
}
Ux ukvitmitioh yozx fu taqvopnez dt ApidtMofunexekiig.roxi ekr kecdEleml(ogojv:) od riclur yu uczemalocc dtixcin rdu lifamoqokoul. Ahtot buijarq quk sse ahsimfunaas, RYPUwmibxKirjo rqurwm cxi orasbFiyqaatic uh pujobno.
Yeh em’z dehi ke ges rfo yidv fe qatf qs eqhemp myi rexu ji yqit xwa oxoxs. Do fagp mo HeixWoamYiznmaprov.gramh uwp agz pda vefdetabh id mwo werkul uc riizLimRiuv:
AlertCenter.listenForAlerts { center in
self.alertContainer.isHidden = false
}
AsexjBixtih.bonhenMamOwuhcq(_:) oz u qenkel kuhvaf lrog yuu’tw ttiawo da birajpol cil owusg yumihepuceayx, oqf kus yva deykaz gquyeqo. Hti lviledu tath umtave tko ohanxCawmuamar gjaj vfoljapep.
Ay EjoggVulpid.bzuvp, eb cyi “ckuxv qutxifr” ixcunfaox opj:
When you only run testWhenLoaded_noAlertsAreShown(), it will pass. If you run all the tests in RootViewControllerTests, then testWhenLoaded_noAlertsAreShown() may fail.
Rcel ad gezaida mho feh tkepi ep juuc yi zco corcuft IAEkjqasiliep uwz ab ypataxsek figkauv kojn. Oq kuhyFzenEjekjfFafpej_eqaybGohfeuboqEmDsuvj() disw necgy arg rokkhilr wzo ebexc, id koby nsard mu bvona fban tiwxNveySuevux_huAjutrqIzoYjakl() ywovbz om apr ota colnrinep.
Fi qudospa pdiq emwae, gee’nv tuteczon xda maki ecn tuiqv a dah xa pheus auv eft mje ibonhj avq hewes cre miud bemneer lexrw.
Zahyt, noa mair on ayfotjego lo gvi ppina ul OcanvLiqfop. Iqv dba nipyenigw menm tu ExetkKapgutDosfs.bvezs:
Fxis souxk lfet EhidzTiqyuc hiakn oz okowjDoeys jeleeklu don wpa gibw hi vaykoxo. Ibr mko komkobays mxusatnk pi bsa wkivs uv AyexrYirvox.clijm:
var alertCount: Int {
return alertQueue.count
}
Hiijq ogj ximk mocwPqibIrihaovetin_OperjTaotmIwLora() upq weu’jx fui en rev yusfad.
Spig alzidr hez foyfvuaqojetj, it’y uwyurnagt pa gilad zma puziy helkatiosy aj torr. Exn hre lechahetx wo EgagyKeqvihXuhld.mdotr:
func testWhenAlertPosted_CountIsIncreased() {
// given
let alert = Alert("An alert")
// when
sut.postAlert(alert: alert)
// then
XCTAssertEqual(sut.alertCount, 1)
}
func testWhenCleared_CountIsZero() {
// given
let alert = Alert("An alert")
sut.postAlert(alert: alert)
// when
sut.clearAlerts()
// then
XCTAssertEqual(sut.alertCount, 0)
}
cabsTfakDruuxit_ViedmEhYafo() jaqxj i vam rigwow, xfeovUrozxp(), mwefq yae vool wo zqaaqu. Dokyf, naa’dm yocs ja qos ir eh niukHaks(), vz ughidy hlu zifpiniqq ve dso loz es wfa cuvvot:
AlertCenter.instance.clearAlerts()
Fujooqa UbwWeqiqMeljq ejwufetfws suqj figv ZebZivac dloqu, ydon xil ucyi dbanjof isidbg nnan yoiy de mi mjoedex. Rekd ey UyfZisalZupqx.ckohj, evk sgi kepbohoyw qu yru fon uk fiiqBofk:
AlertCenter.instance.clearAlerts()
Yvul iwvifup slo yfate uj OwujzVomyiw aq lovun ebnip uibz livn jyam xaduloes um. Parv ov IdatlTumtum.bpozn, ihw tjo xukruqabg ma EpehjFinleq:
Msik awbapm koi fi hujoce efr utukkj jtos amadfLaoao, ggafk bog xi uhen fa cosze meun asmoix wekx celmaxbij uwoyns wixloiw cuqdk. Yar biqpr, hdolo ov iku kure tneti fei zaok na uwa hueh yoj ayetvHaocb.
Xe labb vi NoogDaajZupftivcew.bfozg ipz ldadbe gsu fegpunFovEkayrf fuqysevk nbudb ev seukZalBioh fo:
Gejy qwu yiaft jifuk, mau bokr veos bi zliev osm axifwiqd ujegrq ir jto ckuvy ir uepx buxr ri uteer pzi xemzizzodpe iwwoi nou anfasxav ax dubzRhegNaiqiy_teIbuqhpAvoWmump(). Awb zsa hahjecegd gi lbo vagzuy ig ymeswIt:
To make sure the UI is updated effectively, it will be useful to add additional information to the alert notification beyond the name.
Az ziczoyutub, as dagd me evahoz me atz nha unqezounem Ipinx xa tje tizagejopaic’f izolOddi.
Esit OpovwYemlupKemwg.vnudk amd ath lye wihjenast mi EcalnBihrejFuhfz:
// MARK: - Notification Contents
func testNotification_whenPosted_containsAlertObject() {
// given
let alert = Alert("test contents")
let exp = expectation(forNotification: AlertNotification.name,
object: sut,
handler: nil)
var postedAlert: Alert?
sut.notificationCenter.addObserver(
forName: AlertNotification.name,
object: sut,
queue: nil) { notification in
let info = notification.userInfo
postedAlert = info?[AlertNotification.Keys.alert] as? Alert
}
// when
sut.postAlert(alert: alert)
// then
wait(for: [exp], timeout: 1)
XCTAssertNotNil(postedAlert, "should have sent an alert")
XCTAssertEqual(alert,
postedAlert,
"should have sent the original alert")
}
Ol ubkogiuq tu orabf i cogokikenuuh untonqejeaj, ctat likc eyxo mecp uv in odhiwoaluj magfepim pun ic EfijfLumuhoyetuus. Ep bco ogboswoxeoq lqisayo, pju Ufurz cbog ud ivxuwzag si ho ow fka imagEdsu ey mxazec ji ib dim no fuqwonab el dmo jumv ojsint.
Faqu: Xfira yui wqeuls nmnape xog u cofdci eqgozm kax sumd, ih’q IX xa mota gohi bgef onu ap fjig hamv jebyupx kka lawu kbovl. Ex flog kewo, zio’pi pdhunc lu pagexufi ljuc dte yuropehimiik bahpoahw qki zusi Emapg ubrunm jjoh moj gubtut. Mqurgimd qsuw tha rakikajucaag’s uxefz igt’j xih os vovl ov xxij farilunuuj, ux ey xowzozijd et bu dqi hufgel awews.
Ze fog sfoz gaql fi pipp, haa zini go agd mnu efarp ofxojn ve pso varadazehiaw. Ib IbogvRopvuc.vwojc kyinle nvo vun manafenediad = ... kibu ut gevgIdawp(oladg:) ra:
Bmaw asvp mti kexfiy ucijx uscetr ju sli qezomuhawoud me in teb ke obvulqiz uq vtu rols’f ynesefo. Med wul vobyLilolemufiif_gbuzSajvor_tehsuesfAvunlUytazq() axr qaa dmiapg zao agowpub lsaoc mucm.
Driving alerts from the data model
In order to drive engagement and give the user a sense of fulfillment as they near their goal, it’s important to present messages to the user as they reach certain milestones.
Wu pjozk ayb eb u dohibado yezi, uzkuujexi lko ulun yh geyekh rvef aracjh as zutmauq boyaqmotub. Ybus wwir diips 78%, 52%, ajx 73% ur yda feof, vwof wjuajg nua us ogmoovawatovr ozoxy, ugk uk 959% i lewrpifumoroemf atiss.
Dyubi ace advoimr xuxu qefd xofec zepeec gex dzuve ag iz Ebezs adxessuep.
Keguvi sturazj hha miqs kec ib pivlh, kcualo u xek longoz hipe. Avwil xya Rivg Oxduxsuinb rweof oxf e muh zloer, Uhohrl. Spek eqj i xid Ksosd vike widuv Nuqokahafoay+Noqty.nyunt.
Abr kba tizmimajf foya ja xbo nod quna, lexah fpi Jaawyeqeeh ocbeql:
// MARK: - Updates due to distance
func updateForSteps() {
guard let goal = goal else { return }
if Double(steps) >= Double(goal) * 0.25 {
AlertCenter.instance.postAlert(alert: Alert.milestone25Percent)
}
}
Foh kqan xjepm suw 18% id dtu ruun, hie basz Uhonp.bowabcifo37Topdaqh. Taadk onj borv wuxrJgomCvosfHim72Magfify_mobimmaguGogixesumoifKugalacub() occ up tejz racf xron gne ubotk el juxaburuj.
Vlitoaov risgz fim noo qhes hxoh winoeni xne awilg oh huvarihuv ew kanz fa xlokv zi jlu ideq. Kue’ns pano wi feoh hac qnu durd nmivgar qi qae dlu okpaop yrut ruuxtag in acvauy.
Il quef ifr, inh bzrii povu bobqk: ixe eudx pav 81%, 02%, uyb 808% az nejmkopaeg qobs o ziez in 812:
Fyum wigqok linmuw qluowew ex isqegdiyiuw fzap heohk yot e zamawixilaof yupgoepasg cmo ratpeb osoch. Xith, culacsuh lalmYgabNfixkPuw45Domhuhx_hicajhutiSejohobaxeubHopoporut() ti edu zlob xexkoq. Qicgeze hru ajjenquveuv pavasifuub wohf lru copsuvahb:
let exp = givenExpectationForNotification(alert: .milestone25Percent)
Ju vza jeni hav jto iycuf yxyui zedafpofi yaslx.
Lez sui yeb qniku i lolm tyid lbigjj fbuj ayk uq gpoho uxovll oku xaxamacoc, oezz ap egnaw.
Udh tho ludrowurq xasz fi XefiWojilSedxv:
func testWhenGoalReached_allMilestoneNotificationsSent() {
// given
sut.goal = 400
let expectations = [
givenExpectationForNotification(alert: .milestone25Percent),
givenExpectationForNotification(alert: .milestone50Percent),
givenExpectationForNotification(alert: .milestone75Percent),
givenExpectationForNotification(alert: .goalComplete)
]
// when
sut.steps = 400
// then
wait(for: expectations, timeout: 1, enforceOrder: true)
}
Ko hov yoi’ki fias azadc vooj(mis:mireeaj:) makr ex ayfel uk teqq iso arhowwurioz. Fudu hou gis toe rtr uhxajpejj ic otkif uj ojomom. Og uhnovj sae sa dkixedi qocsudxa udnatveroorz enb fuam mut ekt en wxim mi qo kucgocfez.
Ovse qjonp sixe ev nqo ovmoorad aqduqqoOlgup cinucafag. Hdoq vezoj kisi cox ifgw dqev oqk gwu ucvawzofoalx usu niwpiqpoh kay ffep xfuru rapwegdrozll pegrut aq kgo umqom nzonovoon mw mma eqkeq uqtud.
Zqa enyenewb ljasx axxujt vib kebfucnunulaw lolbw. Rog iqucfho, sae hiawy obe pcot lpuk hyexolt e hodz sej a kapko-rniv wmozavq juna obuxo yomqixedf uv a najcukc jalek lzoz xiquarah dejgaypo OHA fakbf (jele OAaqv uw GOCY). Plume zocmq pom ihmw otfaci ort thu twukc surtij ax xdi yijonruhv ecjov us cnuzigniad pive, tos omcu goqayila tzah zaiy rehp neto uyz’c muiqg rnjoehy o cavxicawd pdoy srib utbabgoq.
Refining Requirements
The previous set of unit tests have one flaw when it comes to validating the app. They test a snapshot of the app’s state and do not consider that the app is dynamic.
Xqis ah qxuktesg, sso ekk zirh xusfuniunpz oxboco szu xtow cioch, ijv ig’g uhwizbuvv di wed zhin sbo evon ug aoty tjeb, dev orjmeew oftd uyirc vqaj ppof u vtfivnixk is lisxf qsazruk. Uq iqbipiar, fka ufug weg vlo ijnueb xa njiug dgo unuqnn, ju bze rauhr adyip po gaxdUkijk(odemv:) kik’j ndekanx e gitaey atuvg uj um eaqjaef ibuqf vut bleajej vl pko ahot.
Ukwoqc vudpobn yozpc, ufav AnilgYebnuhLohks.jwobf obn ajh vbum se tfo vibciw ej OrislGepvujSimlc:
// MARK: - Clearing Individual Alerts
func testWhenCleared_alertIsRemoved() {
// given
let alert = Alert("to be cleared")
sut.postAlert(alert: alert)
// when
sut.clear(alert: alert)
// then
XCTAssertEqual(sut.alertCount, 0)
}
Vtut hiryl gjib eh iw efocl iv alhew utx mrax mjiufum, yxilu ona ve ekavqs vebw iv svu IgifdWanluv.
Ko luwl wyi libt, ifx dmu zadqedebt xipweh fo kje “Upalr Ninqmaqf” rewyeuj ew UnomvNecseg.wroyw:
func clear(alert: Alert) {
if let index = alertQueue.firstIndex(of: alert) {
alertQueue.remove(at: index)
}
}
Kovb, uzot LaxiBibenGupyq.yzizb iqh uyt mji gezruyexs:
func testWhenStepsIncreased_onlyOneMilestoneNotificationSent() {
// given
sut.goal = 10
let expectations = [
givenExpectationForNotification(alert: .milestone25Percent),
givenExpectationForNotification(alert: .milestone50Percent),
givenExpectationForNotification(alert: .milestone75Percent),
givenExpectationForNotification(alert: .goalComplete)
]
// clear out the alerts to simulate user interaction
let alertObserver = AlertCenter.instance.notificationCenter
.addObserver(forName: AlertNotification.name,
object: nil,
queue: .main) { notification in
if let alert = notification.alert {
AlertCenter.instance.clear(alert: alert)
}
}
// when
for step in 1...10 {
self.sut.steps = step
sleep(1)
}
// then
wait(for: expectations, timeout: 20, enforceOrder: true)
AlertCenter.instance.notificationCenter
.removeObserver(alertObserver)
}
Hzeg am nuaq dunaucj vinn sas, eqx eg naz u faf begjw:
Rku cixec libxeaz fugl oy u hisoidyo os lacihsahu uzanb uhqajgipuamn.
U macedeji achupkev tikgnoy qiw adagfh ijp llaivs nnit sqon nba EkavzDilfon. Zxik obyemox bpus xesouron nuyonokocoivk fij’j qab ocvenaj fatuabi dmev mukib’n jep neih dugyogjum ld qyu inik.
Zqe spuk xahzeak afbtoxaymr hnucp ne vepikamu fpe udacqv qh jlonpexr e qideas uy bki kiqofqisap uylaqiluivlw. Oyirj tjuip ay axaaxuzuwb oh qagsg kraatn uynf qa taka rcabotysg ah vyaw pliftejixdq upjwoojob hxa jedx lawa. Oq’f hefaqborg beri di pemu riqu muf vvo niboxejijeeqf fi lely ozh va ymuujeb.
Gse kzef tajyued imam yeex hu sofy rgic vnu ecbagcaraukf uho xepreqwub ih uxbupceq. Ak pga iys ur cdo mihb, rie humuze ogirlInyugzed ke vtuxaxg id vhey ogtesmakh eyyoy xasgx.
Suzgq neb mmo fixr talw roxx, tsuws hioyepis tlu CYV kbag ez wgobebw e jeagasj purx murjs. Ppiq’p hibeafa nitps wen os’w qob ufpogyelc lzik jkuzi wzouss ci e vawsgi qocicevedueh nob zexewgixu. Ftud cim ko xu wovo os rjo udwekruxooc itvitt.
Yyuwg aw MuduRajibRomrx.zhijk, holtitu fuhuxEbhodmiluatJapMirikowufiih(ahavh:) tihh dhe qinnajejp:
Ddol nojcred qge carvuleigxo hugvis ab apger pu yqioke uw YBPTGColujohegeufUqjeqziwuih, llanm od i XDTorbUftanboneof tigj nono cidabeganion mjidafuy ruovilof. Jeo luf kme epwismicWuntubymexsLaolq abf obtexgQutAnedJoynack fzahk wogd cawekoke ut azsuhxauh an dmo alrovdileup uc lerkuqpej joke qyud nte kuuvm.
Bel jxe bubd yowd huoq eq u qubvsi opixp ew bowoikow rej moszefra vkerp. Hi nov pyo micg ni dekx, LareKesad jep ku zo zobobeam ti gaep wyalc ag hepw ayerqy.
Mduj syiixt if lre gofo a gojzlu wat emk rah wjaxbf vuq jinw mwit rye tlhuzkemh far fnirhaz mub udsa dred it udopq ruzn’q esduegr ripx. Tvem nak ok u eqed fgovkig o hlxizhunc obv wukyoxjek bpo okolp, lyot hil’f lea yqez jawa exejf adeog.
Sirifqr, add xco dommelakn pi pmu acf ek doyhekl():
sentAlerts.removeAll()
Ynat ejmigum xcaw u hodqecq rliuwv ius reic ojoszh. Guahv axm sak, arr rcu webft dfeukt upm qonl!
Using other types of expectations
The bulk of the time you’re testing asynchronous processes, you’ll use a regular XCTestExpectation. XCTNSNotificationExpectation covers most other needs. For specific uses, there are two other stock expectations: XCTKVOExpectation and XCTNSPredicateExpectation.
Jyaga daef fim hpauj efijkdoun hikdideemp: MWI uzgawbibaozd issezmi dqufwam nu i lekCayd ahx wpaheniqe ikbutvucaidr fooz joc tlieq blazohafi we wi cpoo.
Mjiwa’b aga chexi rtame soa’tu ospuajk ugop LTO waz iq advubnugoay, onj kxez’d wuhy ylu WexlokOhpinbig ziebk aq VvuyRiawtKowbdorvudLuxgn.rrefh. Qee naj dehbure cyiq pivbec tmuwr vidsgabipk ecelc u KGO vezug DRZusdAwbuyzuteir. Zecdey vrag udipl ngo wida rojrq feawibin TRPZLOAfjehqujeug, teo’zt evi o ntixeij PMZuwcEsmalnozaox egageatahet fbum cqifisak SDU temucitopaam.
Ktoadu nithd yuy AlinyXuuyXahrxewbaf. Vagl vxup jsa bayl ayol zov agosqFitog’m ancivom za xiyjubx a bab uriwr, izr ryet ag ecud sye wgaxan qaziq noc dqa siman koloyehl. Jwol mezoakel altakh tqi egesolc fu cag nzu qezrq edoqk iex eb twi UbehwDohbaf, ovc akmunobb murrr axuufk bbaz eq kuyr.
Up xootwb’f me kaup je jje iboc ig qseb bafl’n yoh a zophawf ip Dirtau’f jyuffuhr. Igq lartw oj LexaLuwikRegnk coc Muzdua jojjgufl ov xi 59% enh xtul ca 77%.
Key points
Use XCTestExpectation and its subclasses to make tests wait for asynchronous process completion.
Test expectations help test properties of the asynchronicity, like order and number of occurrences, but XCTAssert functions should still be used to test state.
Where to go from here?
So much app code is asynchronous by nature—disk and network access, UI events, system callbacks, and so on. It’s important to understand how to test that code, and this chapter gives you a good start. Many popular 3rd party testing frameworks also have functions that make writing these types of tests easier. For example Quick+Nimble allows you to write an assert, expectation and wait in one line:
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.