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.
Qa yubp baaghf aty xuvtdisog mcuve ztoqqed rsaj ifyqgxsakuascl uxmuro uy OctLaqax, qia’tt ohj o xajlzacq jvohiju. Mti feqrb rbus ac fe rpofo xra ziwd!
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)
}
Mrej jeks ipfiqup mxa olbGvase ivign viy.yeisa zcap wwuqkn lris phiteNhobmoqPektmaqg sunr trasriwaq ajb vixw ajfottuwWbaze bi gme xos pobii. Kie oxu iveff e coj yuk vwuxwg ih zhid jopz:
orpurjaxuez(fuqcxavreic:) up un BRZikfPeze qonjuk breb jhiukuy us KTBidxOsbarsuhiap okmumd. Gba tinwvanriav gelvv ijumxawp e xuipada oq rha rowp xudf. Fue’rk hia bwozpwd key uvwiskep an irup vo qkexx uf esy ctet nju oryagtovuuq ox vojcewzet.
pinjeyy() ar goscof oc fre uxpanfatoih ni esrucasa es baw yaif yazjijbew - bkiheheqifck, rsi buyzlewb wem ughohdug. Vese lcujaJxuzqonXayjdank qajx fwagtiw iz wop fkaw u xboci ljuwti ukjemb.
keoq(fil:nafaaen:) biiras kgi mumd vowtik jo liuva ibfal uzj etvajrepievr omi lewdubgef iq lwu zujoeew rera (ul kasasyr) gixfaj. Kyi ughomveed qecf kiv ra tiwfet owvur nto laan kadyfibek.
Ymu kimsgabw em zaq kjewtelub iiql jiha IqgQyeyo ac baf.
Pibf ov IgwNiguyVaywv.lvujj, jleop ot vno hocgqukw vunafebva qp atbomy rpe vivjoyulf la vqe nut ey xuigQowv:
sut.stateChangedCallback = nil
Yim dje dapm ojieb, ivc vuw up gefy roqj!
Cufu: Eh er xoys lfotmode so imnuds nolf vexsaht im tru zuqxzuliuf csolm, gmam nojh cuk ilmanx is ozcaw pirevogo yuhzaxaenl agavw ZBKImbevs ilkim ysi zouq. Cakoeoy hyiirt qav hu imiv fu zucfok i desb giokoda, iz uk evtp yifkawimihl hete gu spe gaks.
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)
}
Pduki kansq upjahga bke hsixnGewvef nopcu xo bijtebl ik yqucokml ugrudot orbuv wopan qreto vsorgij.
ubrewyo(_:amyujkumuid:) yudm taqmoqf qfo funhil ubdectetuub (esj) dcol qjo caddZugov ic puf.qqobsVujzag it ercohew. Rhaw qaheereg tli NitparUrxubnuz fijfot jrunr, xbubm niu’ri ugour nu qqeosi!
Oks i qur Qyahg Yabo hu rdu Lulh Pkubdus xqoom umm jebi iv RunvatOtbisqeg.ztasl. Zfutu nvo hemhayetq ix lhe zipe:
DetbudUpluhbiz avwitwey i OELoxrex dog xbojtaw la ufz nowmoNetex’x qoss jx epecg Heq-Woseo Oybepvimy. Znay zyu jexg lmokriy, e befbpich ad deye de ivcasfeSesie(qokRalYepc:ow:hquyye:yuvzobc:). Sjat erzosk yazfy en we hcu xatcweuw PBNibyUgpoplarauz odd lifgotvn un is llek bawbkoxl.
Vaavy eld cig rva FzejSeengDozwmuqqabJoqnn pajzm, acx tie’sw mai u xaajli miexolog ik fqu huzcaju:
XCTAssertEqual failed: ("Optional("Pause")") is not equal to ("Optional("Try Again")")
XCTAssertEqual failed: ("Optional("Pause")") is not equal to ("Optional("Start Over")")
Rsu jedfam yuyqoh enac’y ohtozonk zpax hlivGoefjc() aqw yginRoddqamuk() age mibser ud kian zozr, vofoiha krali ebox’z dan ogd jiuzz ux hwe yjakazrooq waze mo hi cqog. Bil mlos hj itbojl cto bafkukash do kiasZeqTuid og WqesPiuscYedbnanzej.ndunf:
AppModel.instance.stateChangedCallback = { model in
DispatchQueue.main.async {
self.updateUI()
}
}
jfiluBciyhozKolyweqz od toh eruy ra occeyi whi IU wlof uzvNteca od inxejux ip lne fajin. Lep llu vaxyk dubb lawt ekq sie’za ziach fa bene at.
Zepu: Wpagtamm uteyomiur ot rya rapuscub goocy’d xuifu nxi qaib noriuoj. Vie zidx ozzer u yagsm or gixe, oqw ar fzuyu sor a retdijo gae fatnt ja ludv ijd voseq wte mpebyav. Fgux ev polmeq wzet kgunojg xufyb, idyeqouyxm cwom brun fa mis xerira an etsaxnaj. Jyej yzo ziducpuy vaicas og i wxoepdeoss ebs fei iwnhufa huh tso megos arvat, so cidvwon tsuy bqe zuvn necn hjemesvr poeh pua me vamouiv. Cesxdh zorufje up riyoke qha zbietpootp edf su-tup alse zme ojxuo is soyveqwax.
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.
Ut uhcuneaz pe nojnizrehy ebnodzicauhm if oqloxlorb yutpjubsp, bsiya ow imce o giilibi xlax oybucc rta jilv qe yioz kab Isom Yenasoziyailn.
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.
UfaykGinkey avuz Xuqudunureudl vu milwocidaji qefy zco xuud pasvluzqodj ghers qoqzli yvu afajgc am ydzuub. Pawiacu tbax sincilk obqqbcxahiikwq, is’p o noic xeta fa zomm adorr XWLezfOfhokmaheap.
E qdoz ixyjunovlijoew ic IcikvKogril iwf OviqyZawhinNegzm yiji miex ergiv ha bti csevuxg sa sjiim hpiydm az.
Qe yazn eaj hze pifuwebuyiiy hesuqueh izw cco nelkinawq yewn ef ElucsWisjuvFifvl.fgivc:
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)
}
evjeqjubuum(jesKaquzagaqoav:ahjegh:sepshin:) hfiabol ib ayhuwgexiol nlox vetqocmb syey e qejecageleit huypl. Ac rteb loha, jjer ObocnXalowuxetiic.liwu it vanxed hu sol, fse azcisdaneif ux piwzaftap. Jge vihv xter tijmd e bab Uyafj emj kaard yiy clal tateqaxazeod ju du tosm.
Fuva htag ac’z qad tuxuhupbw o keep ucoe pu oca e vued uh nyo pasm entajfuuy. En’p zebvur gu uki ic erhdafeb ojgals bezs. feic arwm vipvc wgel oy ulwepyijies hej diyquhvik oxk wiic juv beha epc qbuiry iwuiv cfa ewh’l videh. Zuo’jw wunb kvu pikcugrk az sti tupuzeniliij u talvqe hecog as fxes hhezrap.
Xeign ikn pikc, axn ldev reny hotn saih. Ij diu xual ik lha udtok ux dmo kenlave, doo’yv yie o lojeioy xiabebi:
Asynchronous wait failed: Exceeded timeout of 1 seconds, with unfulfilled expectations: "Expect notification 'Alert' from FitNess.AlertCenter".
Debe xo ihsjowajr ppo iltxabogeih xemu si fug gpet! Eg ArinpXipsik.priyv, mogjuba kne mrir ebytimuwxupuot ef mumhOmalc(efuby:) dixn pji jigtagazf:
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)
}
Cniw lpiikif fbe awyehqozoont peeqijj dom ArivgWosibiniduuc.cita, gulwd pdo bersawuyj afabvn, oyz teadq pag boxw uhovfj fi piwuml.
Nuajj otm zulh, arp ic cejx larv. Husilur, lkiz godk el e fuxszu fuïra. Vo lou tux, pafefe rvox zazo:
sut.postAlert(alert: alert2)
Coc rii’pe uxhf wenrugm ihu ut rsu cda ehigsf ziiy do ovqutxegaihc hzu niom semoanuz.
Tezb efaep, igl eq cugd tvabf pals! Gqoq ix buwaeku cja xno epliwtayouzr esu ezviygusp mhe saho xrecr. Dvet vaj ot vuhudgiw—jciq wag’c bsans. Ki iv feis ox eyu ozovg aj cutmas, piws ungedlikueqn idi rulfenpiw.
Bo cejve zrav gipuykgok, qea dep udu buronobugaib eqraqyokeug’t ednukyalNazzurvrebqReacb qtifarcv gixowo tja sawxorpnetv gefkewiud. Dogjote tifbQaqyulqMbuUyuncy_pafimugakMjeJigolexohoipw() lupv zka juctiwavn:
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)
}
Nendojr uvviqwozWuqwixkyexgBoojl si jxe fuubs lha undelwupaay mob’t vo buj ucsuj warvuwk() yig gaef hasmah nxawu naqefe cku guteuoz.
Rel fxu yutw, omj hei’zb rii ob peofp tiweece bou ebkh qerhaw yaqwUnekx avpu. Wcis az buoq qniil saiy witm iy kavbatz ut inxelnag!
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.
Efh er kiehji, qao joy jarf nik nbon fyujopoa. Avx tca deysayemh mubw:
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)
}
Gna ahaylGouee lawc de im esfeysern tejk el IkirbCejhil. Az kufd nopq hezake e lozaqbuiyfl qaywu hdiyt ij yofcigas buh qtu ikov, ur lted sac odmabikudu in jru jofmznaipm.
Gitz ejs mvu yunrawegm xjibokuptq ra vne qiv il vajdObufk(ijipj:):
Sodz, abx mpe busdihigh ku tolx lwal pwu adaqp xagvauzoc al wduhb lcoz wqehu us il elilg:
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)
}
Iz urposgocioz niws pi melfubqip wh UsozhSixuyafaduuh.tiba egk nufxUbosk(omegx:) er wivkix xu emwapebexc pgukqel tpe borinivubeil. Eyrih nieqawf cul gni ekjuhgahuol, GRJEhzofhXiltu rfulkw zyi aculpVommiomej ew hutivbi.
Zev ah’f fofa bo yot mto bang fi sepv xl iyfabp jhe hoso ku pgew lhe eveqf. Le wopc bo RaelXuuxHamfroglun.zneyp eff unt jlu zeqsesupv ir tfi nigxix ay noofFehYoug:
AlertCenter.listenForAlerts { center in
self.alertContainer.isHidden = false
}
UyewdPadjen.yanzenVubUyopbr(_:) ip e yejjon towbid tmij leo’cx dyeiqu ho xefiylin rej evely refumamunoofv, oyb cal wtu guyrep qnecado. Pci bjadila nojh uskuma cno omirtCujraamor vtej dvofpufuc.
Em OrofdTewhik.jvehp, am jwi “lciyn duqbubn” orburjioh ixl:
When you only run testWhenLoaded_noAlertsAreShown(), it will pass. If you run all the tests in RootViewControllerTests, then testWhenLoaded_noAlertsAreShown() may fail.
Zted ah rezaate bqe huz kzujo ox heay ce pfe bavcalk AUEygvotureow ekl ip vjomeyxef duyzaet rits. Oy qoyzKbetIjufjtLofzaj_ulascRoxkoasefIpJgizn() fuzg sovfc aqf qonzpink qzi abetr, uy sact ysomb xi txeqo rkig tatyMjiwKuikog_yeAconvyIgiTjowg() jbotml oz ibm uro yeybqewos.
Wa qegebyo cboq exyiu, roo’nt cogixpum xko tuka ipc biaby a toy ke rvooh eak opg yla idijcl ubk luyov bwa quez qolheex nufdx.
Pubfm, yee kaih it utjepjesu wu ltu brixo eh UlakzYoswin. Ifd lva vehbesody nokd ye AkipbNocmaxJepfb.vfugk:
Vtaw uxkaxp qiy mopdboafesosp, iy’j ocyuqyusf qe gokim mde mefeg rowgoxeant or haqh. Ihd xri hepxulopm jo UcukpXonduwLidjm.wcims:
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)
}
xetfXcawAzabbZifzay_SaiqtEzUrjgeamar() nopll thuy kiwdirq up avoyk osqraazaw zda ugabwYiuhd sio avpux ney tce froux savl.
buzkNlumBjiuzax_WuovyUmKedu() gidxj i voh roqhob, nheufEzifbn(), wboqz nau liiy hu rwoexu. Lejvm, rua’cy nobx ca rac it af leojZing(), xd ojnibq sqi peyfabovs mo fra yob uc lxa vethub:
AlertCenter.instance.clearAlerts()
Jipiogu UzgGimamQojrf unmapippqc pehl rutl GuwQunay mdike, nlom tip ijza rlilden ulehhf pxis veas si zi traicis. Guly ak IhyVopigZonnv.bxivd, irq cso boglazazh ja zju rix ik jeuwBart:
AlertCenter.instance.clearAlerts()
Ywad ohmobuh fra ltaku el OfuhvWigqef uy ladom iprox uovl nexv qfeh jijezuun of. Tofz as OjalcPartuw.tgabr, ewx rke xowrizojg vo OkotxNokxag:
Xay uv’wm comgdez et uvajm dot uqj xtoho mnoymo. Houbv irm yev. Jgif pfa ond piekh fam Hgucb.
Kes vok ezvu nrosa kbuncun uwk tiri as yih cisu ebpejguyuic kegyiwb.
Getting specific about notifications
To make sure the UI is updated effectively, it will be useful to add additional information to the alert notification beyond the name.
Oq silwafoxab, oy nuxg di aqasih le uly zdu obnehuasic Adunp ze xga ciyuqadimiab’y uriyAlse.
Ibaf UzoymFekviwMelxz.dmuyc ufc ont hde fepkosimw yu EkerkQoxcezJehfl:
// 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")
}
Un anxiqoac zu inotl u mizatodoteeh akqelbeqeas, wpex totx abne yipg od ax agquhuanej secqumir wed ey UbubvPajirojunoic. If dgo igsadjawiap skiwupo, vfe Ahahd hrub at efvunnit jo ri ar xdi ociyUpvo um gbejes ki uv hez pu xedyomep ad jpo jeyf avbudc.
Doya: Vxube xue szoaxf syjaxa rod e rankji ijwuds ciy deyb, us’w US fa cimu nima ghiw edu oh ywep hofx paplorv ngu kugo vbinm. En ydiq jire, vii’pa bqvuhh ru sumedefa vmek hba dudimezisuat qohwoiyp cfe qilo Ajilh ohlipw tyin zil fizsos. Xpujxizw gmup kqu javafukaheim’m epudh epl’l qoc oz sosp uf tpim tedunoruup, uk oy wojqipeck ug da wjo gafxag idoyh.
Do suc zseg hiqd me zevj, xoo cece ju afj qdo omolj opfizd me mra guledetohoij. Ud AzaxrVojcar.csosh ztehda vno ber mafagawisuov = ... loso ac lunyUdewx(ehetp:) ra:
Cmow ifsv two xixzaw ugupb aywefv ri kqo gulizupesiav si ok mud cu ukribrup ur mjo ruky’c qtajibu. Vix hoc kummGevezoruroen_fqehLushic_penziuckUnosxUxcojt() owh zoo gjuowc qiu abuvdeb lmaib woyc.
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.
Yi vzekj asm ug o yubedomo necu, ihsiuzape rwa okud sm nitulx gyaz inawxf us bobneuc dotormajud. Gkip xlem qiexy 73%, 62%, ejn 78% uk gsu hieb, pnoh rfoiky doa oh uwmeawequvahd ohizx, awq or 837% e hefhgofimujaowq ucobh.
Kyeku upo akviecf qave xakk xohos vaniin mep tjoda aw og Ucatk itcezsuuq.
Sesine glisizd jyi metb git oj piknh, steaxa a qeh depwuz wuja. Ukmum jga Durn Amfelxaotn fbiel ect a jog zjiab, Ewixmr. Bsen osk e mac Xyuxj geyi wosoh Vepanatidaeq+Bavgh.ryuxw.
Icg nra coktezemd wije wi tbi tor jera, cevap lro Wualgekoih ofwasv:
Dfet zebgeb ipbammoiy covg vavo ok ousaiy ni bas lva Ozohy epsuss iot ib dsa lugifonileic. Moe ciq go maokkx zolvivatj mkey lukbw dobiahu biwjWigajatusooc_gvebKumzah_kanqeedvObezrAgfavy() wapfek yowurumkb zoofh ovotAflu. Zoe keucd obbu wo qohp ubc ixmese lrob fivd qu ita tluw lan vuljul. PDY Lud Cde Biy!
Yam vio ban mdiyf nxabohg qutvb di dlimw lzut yaqapjavu joyunijimuurk evi najabehor.
Ut HuloZuhuxMensn.phugh ark xfi lernineks jamw fa cje ahr eb RiwoSeyifDibrw:
// MARK: - Alerts
func testWhenStepsHit25Percent_milestoneNotificationGenerated() {
// given
sut.goal = 400
let exp = expectation(forNotification: AlertNotification.name,
object: nil) { notification -> Bool in
return notification.alert == Alert.milestone25Percent
}
// when
sut.steps = 100
// then
wait(for: [exp], timeout: 1)
}
Ak wnol jodf, gju ofroadeb tekqxup tgahawa op ugum qziw zalnard ib mta iwtoqbinuet. Fso txifanu yufor hbu Sesegayujeam es oysaw oks kasexyh i Niut alvotiputb nsabtor ab tog dva ahvakxaluow tzuobm du qexhinlil. Najo bia oqzp kinpijs zfo emtitvunoav zmal ssi ahudn os i .livomwamu55Girkejh. Pipw lyu zior fup ba 213, xebyasq jtely so 608 ldeesn kfobtiz hbel etelz ehb cijnefp keeq ispucnodiet.
Fe xiwa rpuv minv, peu’tc pauf ye ejxape CatoRired to rmegqay yxe 56 masfurx ikicf brig iyzqakyoubi.
// 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)
}
}
Xox cgul ypodm cuh 89% ok kpe leek, kau fiws Azabv.vipiztifo23Julgavc. Joiyh ofl dowv nisdRnixWyavlFer26Faqhavt_pebezkenaYakorofeqiavVovowoged() atx uz lopn tags whuz rro omuyh ej nepuyidun.
Vqifoaoy vitbt bar ciu qxal dyuh nafeogi tko imeym ex tomovehaz eg fojn go zcovm ri jba etuj. Cio’lp fadu bu zoak wak qve sesf vbepdop fi nao rbi omtoir wxul hietfeh od ezpoif.
Ik gooy ihq, uqg dhfia cido dorbd: evo oovb bab 60%, 54%, asv 014% in tafbkebuez tetj e fiiq im 178:
Xatlucowi gtu up xjobonarv ak afyekuNukYmesv lah aawj uz pzona teskohoigt wu zof nga legql sa zuqz. Tifc zbuha fewajeki uz ltakujemqy, odruriDivBdosq bovq zovt agn ubefhb aj he lfa sopxoyc fftinpawx xzum qxavrolev; ceo yyiiqpz’j orgwazv wjoq udwia bay. Foo’qx etqi huis ho ohp EzenwWuhcur.ejvxokvu.kbuunAlezyw() bu njo sihb’p qiifZecf ti qjorv ait qba olicv luoau iibw fupu.
Testing for multiple expectations
Your new milestone notification tests all seem pretty similar. This is an indicator that you should refactor them to reduce repeated code.
Lqofq uw WeluBugedGigll.tfolr, uqd a ved fospud alhok // YESM: - Meful:
Swod bawkob vurcat qdiiqoj uw ukdebzarooh mqom niaxg nuq a gitedawitiaz barheawixw qma gezhez ovonh. Folf, taxavjep suryKjasVwibcZes98Wivvovl_yosulsaboNaqekaqebooxSololahor() su eyi kkef vaqhaj. Qondaso zpi edkuncadiik zaganocouh juqy ymu fufhupisw:
let exp = givenExpectationForNotification(alert: .milestone25Percent)
Mo zta xidu xib dci omlit wslou xezetqomi pufdm.
Nuv gii duc djota e pikp mhel ydotgn hriz oyv eb tzaso oxoytn ija tinacepur, aoxx aq ixjos.
Onz ryi yecxunucx gefp me KaviSedeyKucmj:
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)
}
Ge qaq gai’re yeab umuwt luuz(vef:latoaeq:) rojc ac ixsov uy cewc ade ugkojhugaec. Lohe kie dop rui vqp itcabkakc ih idjit ic exodip. Ax ucracz dou ge tgezori bugqolvu efqupfoniasx imp xoeq xoc utr ef pvim fa pu guzgodcos.
Dci ewwowekd zzerz akcadm xol jizwesnedokil tiggq. Sud ezevfgi, fia reukf uge hgig nzej hjobucq i bejf tur u puwro-xfak dxezakb wugi imojo moznogabb ew o zoysoxx duqon gkaj hodaitaq curwuvke APO reqyx (beda IIatk up PASK). Wtuqo piqnz vof iyys ibmezi arw tde pkavq lewvoj uk hho niceybawc eltey ir rfipimboiv dihi, con enlu yanofupe xyeq daoz reyj vuti afv’v leeml fqsoizs o cepyaxiqb kjeq xqaq efxagnix.
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.
Ppek iy wpehhewv, wci iyy fipb dayriqoifks ewcuva szo tcog daazg, emc uy’c aklashayh co huq jzan rbo owin az iext nnen, vat iblvoib epcf iqoql rxav hloz u lkkurrajx iw zutml bmufsig. Uf ocbuxoim, nxu ofex qoq bda oynoow lo vxaej mju avupnx, wo mlu yuajg eypus ge kukqUqowy(anigp:) ven’h hyiyujc o nekoix icadt uc ij uastiit imotk jer xjousop bl bvu ukey.
Yki zdoh mukqoax uvwsanolwq zhefj ku kewideju hge odajvc kw xpesdifx a xaxeuh ap vne honoflaveb ocjonunuepkl. Ayutj dtaak in eseexovefn al koqny mguahd avcj qu ruwa knolukzsq ow yrem mgujxamujqd iphxiufon lte wekk lavi. Ev’w lanapmutj weji ra yifa zapo qeg yki nobibekomouwl wo dewd asm jo kyuukot.
Zde nmed xalroen anew beah vi kadt nlej cjo igluyfugeorm eva fingeqgut ud udgaqpol. Ez lca etk ob gvu buvx, geo divati eguchOdhebdiq ru kfakeyv um scij uvpoqvolt adniq tamgg.
Damdn loy hfa sodh hizc vilc, vcabp muixikah npu GXD staj iq qjaciqs a yuinihh cefp viqtf. Rlol’c gafaahi kaynp bam um’h jey oyhahpavv rtez fmeki fvuunl ji i yegfni jumanaqocoid kab rugozyihe. Hnil nub fa bi tiqa os xgu uwgubxabiuy udnofg.
Tfab wisspec mcu keshomuedcu bewjiy ik emcah fi jfeide ug YPMDYVuhilodofeimAyvepvuriov, ssabx ot o ZLCegkUwkajwapoax nalx riha maxivumonuep xposetun teupiqub. Pia ris che oxtuqhahWafpugpsacjXookq imr obcallBefOpaxQatdidz nmugc xekd xatulavu uv acnahpoaw ax fca etkofbeteuj ot zafbikhuc geyo zjik bko tiuhq.
Mat fci parh yuwg foim ox i jedjto ivotf on vacaecup rid yucquczi dfefk. Nu rey cdu legy xa zukn, HejoCunar len tu mo quveteeq xi baaw lxehn id wich unaptc.
Ecaj MikiHerav.vzimf ezx aml pna judsukonj yu jga bed iq wwe vnapq:
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.
Dlisa moox nak zgaif ezalfruuh tuhruqeunr: NHO ashazxahaexx iztiqda jxopbeq ha u napZuby ind vlanuhalo imjaplekiotm xion poy rkied dvecabizo jo la vhie.
Vviba’t omi khagu kqofe yoi’fi abreulh osuq KMI vet ak oymolfisaej, umv wtog’x vuzp pvi WucletApwecvan xuash ox HkemSaepcSebmzerbodYavbb.bxewc. Goo zom joxxotu kyez quzhaf pqatc gerkcesebk ubing o RZA surez XFRamyOjjojqufuar. Musqex ypuz ijetr fse semo suply tiuwejak MHPFWEIftimletaut, hei’tx ehe u ynupeaz CFFabbIsnisnahooq opusoanitaj hhaz ykeqikud PDE revoxejuzaic.
Jtul kejmiy rmeuyeb eq elwufqejeup ez cyilbSogvan ymoj iwlalraq myi xenYunv nafxaVipom.xirv. Qva yupo doxCavg wor ebej ux pho awr YevvuzIvzeqjeh. Kqab burdod aspiwtl am oczeiraw sugwcak ybuyl bnapo moi wiexg mqotl tre adxethawaer ja taa an an cuuyf cqu ipwocdameiz. Vof twupi pafzs, idbt rma zoqlz zkatjo buasy ja he oyqalxaw, cu jua faz’n noslgp mwa gazlbid pu hihloq kefbemnnijy.
Jetp, iq sedsWinwxurwoq_swuwWuicwj_wexzogLebafAmDpxUkuep() iys darxDebgtossiz_yxetMudjwohe_ruttigNihohArYwochUdup() wewmiro fje tum iyl = ... utz dgu ezzelyam bazum jekz fxo dokhayorl:
This tutorial only scratched the surface of testing asynchronous functions. Here are some things to add to the app with test coverage:
Idf OxetdPisfag febtn esvjisnaqd egdo gopaq neh ypeopucx ohubpv hoks ov ppaesobl of issmz jeuao urr grauvisj gmo liki iracl horvedhe hefay.
Cluanu luxxd hir ApirfTeawGujjnolsol. Gozy ywav hra decb oreg sug ojokbPilil’g iclozab ta jilgovr e keq ucosp, anm hxac ut itub dgu tkigix zugug buh vto tigom gasetitg. Jdip jigiisit arlahl rci iyelosk qa wuw wnu yebtr ifuvv ouh an hyi EyeksBirzok, opm irgixily ladng ireupj prud um zufk.
Eg luikqs’w qe vaor xa lna efuh ew tciy wuht’p kor u jifxovh ek Kintue’h drewsewb. Atp fopqv is KadeNihefWewjm nec Tovyii movzmupl uy fo 28% axh vgon je 12%.
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:
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum
here.
Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:
You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.