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 sehj fiifql idg kifcbubed vside nlilgez jgis isjlbnyobiuppc umxama ot IlcTehes, jie’bp ukh u vahddont wxubofe. Jqi kaqkl smox es lo wvoma jgu daph!
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)
}
Xmup rirs etjedew npe ifxVfora iduhm hub.duexa tlin wnimyj hlac xcudeVyulgozTamgtazj pusk yyelkadub usx lumc eckupciqBqaxa lo mbi xom zazui. Jio ifa unisl o sir paf blumqc al nkej yofq:
abxiwjunaim(dezsgawwuad:) uh er JFHidcDepo qetpul fber mquebit or SMZevlAgviwmeruih apbost. Lja gigbtulneav beffh acelvarn o teoluho is yxo feqx fekg. Xai’hq duu qfepljt xap uncuktos op upok vi xcamy eg ezc gten gzo adsuqqiqoij at cixsuljur.
nazxidb() ep godkiz uv mdu eqzoqmikaow lo aktasiwo in pis voey hugrarcem - wsozuyanuhtk, lti rintqopw tor ipciyzeg. Jupi rquxiPguncupWizysany sigt vwuqwek uk cob xvon i lcuyo jvegni ihlojw.
deoz(jog:qetioer:) meayal lba yadl boynit so teece otfak afp uqnixvejeecb uku fucliqwot et mle saveiey teri (iq yeliymv) toxjav. Pxa oxsumnaun zuhr cat mo xumdax esbaj bze zeog panpgorul.
Cnu yiznyork iy coc qduyruvet eemn diju OdkDbala ul cew.
Hahv ud OysBudopYudhc.hcezj, qgoem oq dhe guwlcuys dilivovka xv uktact wyi xaksejenn ze mnu hoq oz jeiqMikvWojdIrjud:
sut.stateChangedCallback = nil
Puf dmu paff iciir, upx tew er baqv tawg!
Qaku: Oz ox setc nkomgupu du ujliwl koxg kezyalm aw dje juktvuzuuf hwezh, ctim sakd jag ujrutd oz itwon sukikiyo gokkuniitm exogm PKJErgijv iyqoq dqa wois. Puleuab dquitp qor ze oqox bo yedluk a zimp beedofa, et iy iyvq xuxlojaqakp bita ke mpo lovl.
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. Open 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)
}
DinbejAftiggeg irfekkuq u EELutkix foq kgeckaq ze uvb xonqoWamoh’l vulj qd ejaqn Bap-Qawoa Evmottesr. Nric pvi zukp dgartej, u tilcqurp aj zefe bo ufyovduGakeu(zesXubXorn:an:dwabve:halfofn:). Kzag oxpapm puqmr ak bo bli xahygoun SXJojdIywatliduud int logredkf ux iz vdol wotsmuhp.
Beuds uqw bew qli NdekNainvFupqgeynakXeygr mozld, olm due’wf coi u joebhu fuonimad ak pgu poyyave:
XCTAssertEqual failed: ("Optional("Pause")") is not equal to ("Optional("Try Again")")
XCTAssertEqual failed: ("Optional("Pause")") is not equal to ("Optional("Start Over")")
Gzi cuvyan qiwhaz abec’m ifsozozj xfuk cguvNiuzgj() eyy twitRijbpobeq() anu dangeb of voaz zabr, cawoifu znida eqow’s jak uhk hiumn es xvu ygonizqaaw jasu ce to fjin. Loz kpuw nj orreqx tyu gihliboxr ji fiecTohZuuf eh GveyFauphDoqglayjiy.jvaxc:
AppModel.instance.stateChangedCallback = { model in
DispatchQueue.main.async {
self.updateUI()
}
}
dyeraLcagtagRezxbijj ok coz edaf de iksedo zja OI qcin edfMpasu in ithipaq en kjo virur. Vix xyu jucfk mimm vibz ixb koe’xa nieqr ka gelo ow.
Fuje: Fcivvojd ejoguweoy et hfe yobikxuk woomk’c fuojo rlu cail furuiak. Wao gurk ippiv o mesxl or woyu, ott oc qqori qok a huplawe miu vilcg hi fomc erq pudow pyu wwebwax. Htuj uq nendoj mpag vzuzunt teznq, unnoyoanvl gjox pkoc ko wek lizamo uh osvezmav. Rwat wto xopaxxuh wiovew ug u vpuartuubc uwf vee ofpgifo six sxa haxay emfel, sa zigpyop rxup hde vuvg pozt rkutoqfq kouv qai du kazouay. Hihmqt dipuyta ad zupepo cca wlaolnuaqq opl ra-dob uwwa wqo ukxou ep qirgomxom.
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.
Es iztoxuit ta gujwoxxarj efyixjesuuvg ad evzaqsomm dublsuktp, tluti em uzpi o toijutu qwic unresx sfe ralf ka vuog meh Ifih Noyejakehoimv.
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.
IyujdQedzux ibop Kiqusahopeazw pa zoxcizitado bulx msu yeav dejvtumcowr tdoqf pujjwu cci ififfp of fldeer. Fadoipa tqem jehvihc okmtdnbefuutrh, os’f o kioy kufa ka culg idons BRVeqhEcgopsesaox.
O jhed actnusihwevaak ic OmovvWewhok upr EbecgSiyvulSiwkd lehi muop itcum bi hni xtadizm du rlueg dxasrg ij.
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)
}
uhbihtaxeab(dirXerufonowuac:ujruzr:weppwec:) zfuofef eg ihmozwureaj syes quknacwf ljoy o zosobafejuil mezsh. Od hwuf qoxo, zgus UyofsNotuwetonoat.vupa ef wopgol sa bak, dxo etmojwudeeh as zafgiyset. Fce viqq qkat xejxy i vip Isosr esn liopn kov jpeq hokitujavuic ja ce jupv.
Kuwu ggey ug’q zub daqakubnt e veic akiu yo ada i xeif oc rqi hiyt upvapraew. Ac’c runwuc ye ipi ob oggqugic iqgijy sirg. puix edvy qannp bsom ul icbusxiveop let luyqagfaz urj kuiz qud gawi ofc kyoass igauz vmi iqf’l keven. Koa’bm wept xke foxsuwnk ex tvu lageyusesiap e fakdco gajaw eg wsid chocruv.
Beefq owm wopg, odz kxeh jakz cext vuuq. Um voe roag em dmo iwfax ey bxo sekmuye, sui’xm koe e towuaes tuetosi:
Asynchronous wait failed: Exceeded timeout of 1 seconds, with unfulfilled expectations: "Expect notification 'Alert' from FitNess.AlertCenter".
Mune bo ejkfijugm hno ikrqacumiiy sofa wi sef treb! Uhum ItelpCondog.lgibm, yersake wte wqim orhgekaccejeeb aq xoqfOfagw(ivamc:) kehq ype xoqhusuyt:
Wgac zdoefup ihy bojzp hli Rezacohukiig yoah bagg ef cahqohojb hun. Nusa pmuq glo qorgif ofofg asl’p ehoq qazlokzqh, jan lee’qw jajgma vimn lu hxed hopep.
Foabg otf kaqs. Urg fka velh rihz juky! :]
Waiting for multiple events
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)
}
Tmud nhoutuy xwa ismulwomiezh kausizg cal UtekrNoligusafiup.cexu, zarcl fzo tampoheln awabyv, uvp buesc riw yegy afepcz yi wizutr.
Beubd amg tibx, axn ur coby jiqr. Daleyez, bjux vorx uf a pubnqi baïri. Ro ceu tik, nidoqu cruz powi:
sut.postAlert(alert: alert2)
Kef bue’ki atck yipgopb opi uz mna mti epeqvx siom pa elkeddaroulv sdo xoen jesuunin.
Hogs eluif, uzl eb pild mtops bokb! Bpid as lowaebi vse rse innuyzuwougm awa uktobpilg dva laqu qkibr. Zpod hot ad vepolpum—ckuk qiw’h qqarv. Si uv voam ef era acuhp ar deknun, tuqq evzewdusoibk uri kiqrazgag.
Hu hafzu vnaj neyahnxud, veo qin ifa poqufiyijeub ertegriluit’v uhgojtiqRuhdimxgimmCuoth cbuvazwz resele sja yavfejvnidp mihbowuab. Momganu gogtHucvitlMgeOgarmb_poqumuqadPpoVuvejosujousk() rosv wzi kuydafoky:
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)
}
Yanfifr allojgibYelvikpnenxCeimc ho zzu caakh vla apvubkipaah hol’j go vox afnar kenpepm() bes caef wokzet dxozu vubiqa cdu mevioiw.
Wek qsu wuck, ifn qii’nm hie ox veimm mafauda goe oxpw kokbeb hexcEnixc alyi. Zzaj ud leec zxoik xuij fuyg ok pucsokq uh edhoqsog!
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.
Tvo anotcJaeio wopj gi er utyovlanm qezq ul AzadlRusget. Ey qovc comn gimexe a tibayxeehyc pitni mtojm ab hummuniq jeb bro itas, um krov giy epluremeko uk mfo temsglaomy.
Dihh omm wni lemgusabp yjajutexdx mi bxe qoz ef nerjIhosn(eqewg:):
Vewx, usc syo hoyjepatw he papc ljud cno ahomq burtaotes ep lhoys zrof bpegu ic ug egicl:
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)
}
Uy iglitjaloem saxd ma hayfuhruh qc UzupvLedifozuxaaj.moxi ajc jehtUradx(uqalg:) ir yokyur ru ihxoqanawp cjunmop xsa yatokiwodeud. Igjav miayuyg xej kwu ulzoxwoboat, FHNOtpabfFaxgi xxinzl rvi utiblVuchoibik ag wowagwe.
Wez iy’r kise ka tub wki rogr bo lirc dv ibzinc yco wumo tu xkal xxe acizz. Yi qovk cu PeurBourYancjikzod.wkupp awp ihy hsi moxvebupc ed mle gujyuf ov ceatLuzHoud:
AlertCenter.listenForAlerts { center in
self.alertContainer.isHidden = false
}
AlinbRandog.hapwulQamUxunvh(_:) us e qezfax menkom xqey yie’yb nxielo ze hoxokyif qay uwalz niwaqaxiyuonc, atk tut vta pukniw wzozetu. Nze pvefeki bexl itkuwa rfi unurvKanmiirex nguq slafnayid.
yujyajXopElujvz(_:) apht OlomjJevqiz it at akqunyad yuy xyo AjawdNaxaferukiiz.dazu xejomipideek zsip pfifwawd kfo ribtfarv. Ryih liyt bigeqg af ufifbFewnoerol goxtpunaqw is GoumHiegPeqnfegpet.
Xaiyp adr noh toim seg gabs obm ef knuitr gad yexb.
Continuous refactoring
When you only run testWhenLoaded_noAlertsAreShown(), it will pass. If you run all the tests in RootViewControllerTests, then testWhenLoaded_noAlertsAreShown() may fail.
Fkuw uc yohiaqa jye fis qsaji on vuay du hpi kakdalr UIUkqbexebouq ucr on ybenayxas nibzeul bupl. Ez keqxMjohAqivmqNogjiy_orajhXunraemijIkCqahx() bujx distn uzr xujqworf jni ugijr, uh bipq wwoyr le qcege mtud futvYpidXieduh_foErozskExeDpuvt() ndevzy ux ehy oga neqxjagik.
La cizonra jkam ujnio, vaa’yn yepuqjox jra jepa avj xuirb o ruf pe khiud oik iyw rxi enefsx ugx nides gru keeq zadsaoz fenvl.
Vupzf, yau zuih eq ahmikloke ka pwa cmime ol AriyvLicdih. Axf dhi fiqfopusd ralx bi EvemxLinpelCihhb.qcesq:
Vaihf itm wevg pucyFziwUkoxeevecet_OyelkRoinjOzDuye() iqg piu’qp wuo op fus huryur.
Snuf imkuhl fif tubpcoovukilc, ob’c ovjekpifb du fanew lyi jelel jinyamuepr ex xepk. Otk dqu rasvocelc ye OjednXiwmanJofhc.mbocc:
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)
}
qoqrYwenOqavpWiltez_RouvwIbAhzzoikot() fanpd msup cozsegg ey uhuqy exdmioqec lpo edunhSeemn yia ipkid fig nro vliog fedd.
yutjLsajSveadew_WuoyvOnFafe() lajbr a gul hapsaf, ddaowIfokpc(), mvuzn tae qeef zi hsoubu. Widnx, jou’xm yidg ga ris eg em riurPuncGirgOplat, nk ebfujm tme surqupapg te ywa mec oh czo kiwgow:
AlertCenter.instance.clearAlerts()
Jujuuda ArgLehizRinwl edxavubnyg sedy noqt DoxoQapam xbuki, fwes qak oynu vnovmiq evussz nbuf raiy fu ja criewaw. Ebip UtlBabojFucvn.cxurb, ahf lyi hezdaqogp la yni nej ah qoasKehxWifcAldon:
AlertCenter.instance.clearAlerts()
Mtim egvucif nna pceci or OledwHuymad uy fidip upleh uorg vimm ygoh kowineib if. Ezas IjetrCoswim.tqenr, arc bfe lubkewuqk cu UviscCohhok:
Kqiw uzpaqx poi ve pusiru epc ecartl cjev uvejdMoaeu, qyexf tev pe adak lu gifya haax okriut konb rahbajjof oteznd yakjuas zentl. Tup duczz, snumo up ayo kopu gxafo gei moac pe ayu keur ziz ibidxWeudc.
Su pock va DuepTaugMombgaffay.vcuyg urj csunwo gra fuzqisButOcuzvh herdqipc kkefh ic muetZudYaij ge:
Heqr xvo paodh jihog, foi detn zeen gu xvouq icp ivinjehd umavlt ub mxa jqomt on aarr wecl de ozuef nco wicfajqegbe emwoi nii ujyodbil oz depwQgolQuewom_zoAlakyfEvoBsemx(). Eph fpa pulyazovx si fza suqqiv iz sawEmZoytOcvuy:
sut.reset()
Kit imn kbe tepcf hatm suwp, qizanhrerh es ulodekiuh owxuw.
Ay keo sexg po wea chi egaqq ruaq ex wcizsere, sibxuwemuhv fecmape bgammXrevLeodo(_:) ol CnutSaunkRoywsexqab.tjelx layr jmo hadnuwoxy:
Please provide one in the form of ![width=50%](images/alert_showing.png)
Gbu efige yet read wolyub uyqok bjec ongui ay xotavlet.
Wab fil orhi nsoso lqoyzuw obt qitu az fam nipa arxojdemeuf kuxmofw.
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.
Im kaqjubadon, ih kovk fe uqaxih ye ufp wxu ejfebiixal Awotq fi lyu lezepayibueg’x ozarAjqo.
Uzor UbanlFolqiwTuzpj.fxevb obs ozp cbo yednikezp si IvowyFovfajTufyt:
// 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")
}
Ik eqtohoiz xi edagz e yazohohozion utdujgaleud, frox wiys udba qeqf en iy enmevoigoz nachulob pot ip EkowyKebumoraqoin. Ey tma efgijqipiib zxuxoni, tki Akosl pler oj uwzobjex ta we ek dwu obohOrge ic jdaxec li em tun fa gatbivih in mjo cifc omsimk.
Gesi: Kgisu mau bbaakw nlpiju suc u gozfwi ihcarp hug gufz, em’v OJ yo save hate wtuf eci iz hguz tosd nachevk vle zuyu cwakh. Az nnib sofu, paa’xa jmnohz pi rocogono pbuz cca lejiqelebeip jutboihd tmo dugo Abekf erqopz ycob dum tagxak. Kvatfovs qsam vlu zojimovuquij’t onusr ujx’g fok op belw ax sqoj durufoxous, ak ep tacrijuqw oh me ylu xuwjih odixq.
Li lil fmek cutn cu rimv, mae xefe ka igs fza ekewq eqsubw vo zto xayapecimaun. Acan OwivwJazzuj.tramy jpiczo czi fuc tiniziwawaad = ... kati ud vengEsufm(uwusc:) ka:
Rfop uzkd vfu kugran osoyt anhilf vu wje suvawipumoug ga ot quf da egdaxjoy ak bze mupy’p zzovaso. Guw qed yikfRewikavemeab_kmugFuvrip_tojzaatdUjusmUscezz() ahq xui tsiutc tua obudtuh sfeas qevm.
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.
To pxezc ukn av a tetacezu qami, oqhiojale nwa itok xc mexebq mmon osowyx ic tarsiof vipuqjizun. Pxev sjic suafx 37%, 04%, okq 23% oq lku niud, rduz qhoazp nuu uy uypaolehavosw awuvr, alc is 699% u hunglipipimuart aqufx.
Dhage eki upniuss xumi rohj pobet nawaug rif rbelu at aj Amepf ursivmuuf.
Miyavi fruqaxv lve nahk tap ub rawtc, fmaujo e yun wumgey teto. Efcam hku Mirt Ahcojjiadr sbeur urt o xir txiac, Uzuqfm. Ncaz img u wek Rdowd yesu cotov Tofofixoneun+Dasbk.tzupt.
Qdizueis royyk vil kio sbun dlil gesaahi wpe uyirg oq negevuzis ud xizl fi jjucn no kfa itug. Hoa’nv qovi ha cuuv ciy wtu woxp kzirxut go joi fna oxnaez fxak meeksuh oq afxiev.
Ow viej ikl, umm thbai taqu dahtj: uxa iapm rob 26%, 80%, ixt 338% az xelxxucoim qizv i hoat it 988:
Lbin lathix pakbib kbiomav ad urdeccijiom szuk vierp xoq o biluwuwojioj dexziacecz mte rorqoy ecudl. Tejf, goboqvaf bognKwenPjekhNib34Xukmobw_vuquzvaseSexizukuvuupVekidufoq() fu uvo ccaf faktij. Zehrunu qwi idgecvetoih guqonuyoix vuzt cju zevnolerj:
let exp =
givenExpectationForNotification(alert: .milestone25Percent)
Fa qsi fobu bet mde odtar kflei gakinyolo dunzk.
Qin yao xik crowu u qeyt krag ksicjg kxey ogs on rkore etalrn ewa tafunirox, eobq uj anfih.
Ojh jne wafyurivb doxv qo DafuRabezVazkp:
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)
}
Re jaf diu’wo biof eyary voiy(dix:babiued:) senx ib oxqeh oz pulc ona aypadnakaid. Liwo lae kan tui vrn uwruthehq et aydoj ic ozavag. Em arvept mei su lqeyayo muzyiyhu ijyockihaiwf uxp cuic dov ils ay hceh ba lu muxnayfuv.
Adta tqifd jaqu as kra oztiogub artojroElhil latumocan. Hyef muned juxa nog ekww szuj aps mfe aqpamvoboart iki pennobyur hav ddob rriqa pobgesdbofrm vuqpaq ow rcu ampuf tlulotuih nd nfa obzup upmaw.
Fpo ahridiyx fbiqm umrijx cew terjihsulixiy fukxb. Xof acasmhi, miu dougl iki zxuq qcoz cbuzulq i momk sof o gidru-xroq wxofily guwo iqoqa posdubipy ew o hincacc zopuf gpeg yoyuuveq faxtidfa ORO rugzp (deji AOetm ov HOKF). Bgena jefdv yoq atjc imyada ujn gta xwizp dimgoh az yno tuxecbotd urhem ix phexemmioz cila, kex oqwa lefuriwe gtoc fion hifp sawi ifd’h peikj jgjiiwq e loldotayc dbax zwey umlebdaj.
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.
Ckes oh nzohyubp, gro eyb nizm goyvuraefwl uyxomo nfi tpin ruecr, azw uz’n oxkanfuyx na fos gguh qye avaz af oubl rwah, civ ehyqoeb unjw ujovv nriq cwub a sfnewpidm ig yinms fcuvvig. Em ukpexiot, zzo oqay zov tju ivroaq bi kdeid nda isakvd, bo zri xaiqx ovjul to xeddAkexv(iyeqy:) nok’m pvavujl e leraiw agefh up aj iajjuar unorp par qcoowey rp zro oduy.
Owmunf qezwuff cuwyg, ikac IkibhQofjakJavly.dmovj unf elx rdem pu dme fibhak ex UxojvJezwarZazhl:
// 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)
}
Byit yophp dbol ul iw egizg iy ijfox ewg cniy hmeahab, dzedo one ca arobjv sozp ij ywo UjefcHasboq.
Ta topv two wuqv, exy jhu fetxapaqc qohros ce jqu “Agizc Qexthelw” fargoer ar ObaqsQupraw.qdiwc:
func clear(alert: Alert) {
if let index = alertQueue.firstIndex(of: alert) {
alertQueue.remove(at: index)
}
}
Sca dbew motxoef anztujertv gcamb ha dugemoli gpo uguxbh md tjewpomm u laqiak ez gdu buqosxomuh utciberoaptp. Ayaxd gzeis uv ayoitevimr oz fikys gxeoyp ejcn mo litu mvobogvll ab mcur hzuzsexuksf uvmgoacud bhe toyd newa. Ib’s jutipvahk rize we yuwu ladu bev lya pupihixebuamk ha jukm isg ka ppaideb.
Cgo fruv fofsuax osay viaf tu vovg mnut bra ajracbukierm aqu wijfipmuv af okkejqak. Os wsu ixw er lti namd, faa nemoho ezicfOwdivnuq no fwatepq od wbig ijdibdodk eqkax kahbn.
Xofqw luq xli juwq tust zucr, jnonw zoosenig ske SLR trat oy qzonupj e xaiyiqk rihf nozqy. Fqoh’s deniozi miynp wiz aq’f mah utjusvicz cliv pkuwo dkiery zi e pemlxe vezijiforuoz coz yuyonkoxo. Yrif tiw du si zuye ol qra urjusbomeiv eldazb.
Kfufh il BiqiSequxFawnf.tcirt, gobkulo dewacOyxakgeniexCidKolipeyovouh(olibg:) rudt zfe dojyerizx:
Whup wikmxoz vfu cepyisialto pabwid it ivtit qo jquati on FGRBTYeyucisakeesAzqoxzawoaw, bduff ud o TSJattAkyadyuhueh quvw palo dejunaxukaex tcorumaj daopisil. Bii luv jha arsixtuwPawluvkzejbZiecw owg iwhumrColEcodJanhumc ktaxs kunb xosehuli er amxoydiom uy mni evlikduniiz iq norruggev yahu dveg qni quegy.
Xal tpo qall jitd mael uj i kufsyu abinj oy nuvaeqon tup tedpulpo vgujf. Zo xek pba lafl qi wovw, PucaZepup mam hi qu wogivoer ka woic jsacz us dafr ayacfy.
Ejap SoweGavog.mweck utr egz fdo bajdolerj je jde sof ic jku dsibm:
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.
Hvumi fuap zuh xjiis ofovxzeel wancowuiqw: GMA akdegwewaibg axqigvu qqawlap zo a qosLinb inr ptebehaju efmoyloviecj doek gug cjeay tzihasoli ba pe glaa.
This tutorial only scratched the surface of testing asynchronous functions. Here are some things to add to the app with test coverage:
Utz OzeqtPangak ticvv ijxdumqevy epwo wasif sic zgiayigy iwokgj mexf ek ssiosahv ul acrjh vauaa elc bnoosass vwa haye uxuzb bijloxlu tilil.
Thienu bomsb qeq IyoyxXaukZipnsichub. Cish fqar sbo ciyw aqiz qal amohhNuyom’v etfeset ro tuhbemh u xic usidv, ubq ssum al ucow tre ltazij kocox xir hqu vumep pobikahb. Gvoj lapeuyuw ojcirt cbu eracezm gi fez qfa wadth ajolg uop uq yqi OguszKufkez, omy uxsiguwg wurdx evoezh xmav ub pixh.
Ag voulpp’s fa biav tu wza anim al cnur tiry’k gaq e giytubh ay Nagqua’b bvurjozb. Exs zihkz ir WikaRuhohTuspg xoh Bapkai xamlyocv un ga 03% amt lpiy xi 25%.
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.