So far, you’ve added a bunch of interesting features to Blabber, including a chat feature, a message countdown and location sharing.
As a developer, you know that adding new features gives you a sweet adrenaline rush, but quick iteration isn’t always smooth sailing in the long run. In this chapter, you’ll take a breather and add some unit tests to the project to make sure your model behaves as expected.
Testing asynchronous code with Apple’s test framework, XCTest, has historically been complicated. Without language support for running asynchronous code, you had to rely on workarounds like XCTWaiter and expectations. Additionally, you had to wait until the test under code was complete before you could verify its output.
test executiontest setupXCTWaiter.wait()idling...test expectationsnew threadsignal to XCTWaitercode under test
From what you’ve learned so far in this book, you might think you need to do something complicated to make an asynchronous context within your testing code. Luckily, you don’t! You just declare any test method as async, and the test runner will do the setup work for you. The test suspends at the point you use await with an asynchronous function. Once it resumes, you can verify the output as usual:
test executiontest setuptest expectationscode under test
As you see in the diagram above, the new syntax lets you write asynchronous tests linearly, as if they were synchronous. This makes writing tests much simpler, as well as substantially more readable for your fellow developers.
Note: You will see that the improvements to XCTest are rather minimal. Testing simple async functions is straight-forward but when it comes to more involved async behavior, sequences and streams, you’ll need to build a lot of the testing infrastructure yourself.
In this chapter, you’ll work through both a simple test case with a single await and a more complex one that captures test output over time.
Capturing Network Calls Under Test
Open the starter version of Blabber in this chapter’s materials, under projects/starter. Alternatively, if you completed the last chapter in full, including the challenge, you can continue with your own project.
Next, open BlabberTests.swift, where you’ll add your tests for the BlabberModel type. So far, there are no tests. No bueno!
For the most part, BlabberModel doesn’t use simple input/output functions, where you can simply assert that a given input always returns the expected output. Instead, it uses functions that crunch the input data before sending it off to the server.
The full chain of events looks like this:
“Hello!”BlabberModel.say(_:){ “message”: “Hello... }URLSession.data(...)dataChat Server
Your goal now is to add asynchronous tests to verify that BlabberModel always sends correct data to the server.
Good unit tests shouldn’t depend on making network calls to an actual server, where connectivity or server issues could result in flaky test results. There are two common approaches to testing networking calls:
Injecting a mock URLSession-like type that captures requests on your tests’ behalf.
Configuring an actual URLSession to behave differently under test, letting you verify the requests from your test code.
In this chapter, you’ll work through the second option. Using an actual session object with a test configuration works well when you want to test that your model performs a given series of requests and handles some predefined responses.
You’ll add custom URL handlers to your networking stack via URLSession.configuration, which lets you do some nifty things. For example, in a production app, you might want to catch and intercept all links that start with tel:// so you can make in-app audio calls. Or you might custom-handle URLs starting with https://youtube.com to prevent your users from switching to the YouTube app.
These handlers are subclasses of URLProtocol — which, despite its name, is not a protocol but a class. In this case, “protocol” refers to the set of rules for handling a URL scheme rather than a Swift protocol.
For your tests in this chapter, you’ll intercept and record all network requests using a custom URLProtocol subclass:
“Hello!”BlabberModel.say(_:){ “message”: “Hello... }URLSession.data(...)dataawaittest setuptest expectationsChat Server
Implementing a Custom URLProtocol
Open Utility/TestURLProtocol.swift. Inside, you’ll find a bare-bones URLProtocol subclass already waiting for you. During testing, you’ll add TestURLProtocol to the URLSessionConfiguration to intercept and record all the network requests.
ypisbXioqikd(): Gaxi, jio lauy mni jejuuqy icy yign a desxutfi yizn wo fki jyuadv.
grajMaerunr(): Cikq hsod meqyec qvob jxo ubefovuuc un yuhvemuv em tyax yxu rusxeiy mzaazh ahdurviti dgav jhe yudiigf. Woy dpofo cojrf, quo yis’t hema yi usl ofjfnowf coqe.
Lke zpalnob nuwa ir sgerhTaegonb() broezet u fixmuqmcan bicmok juyfegvi borx nu sawyisn ahl hujeftn ad zu bro bmeusq. Gos nnere punzs, rii’no orgf amhimuyjak ed lwa uuyruils gelaesvc, dig plem qaxet jevm xdeq dqo dovceb. Rue’bc updo xifoyg xzu kazcigy xikaenwz cita.
Wajk, ekl wvat bap mkebavyl xa qqu ZavdELRFyehazug pyko:
kqeqip heg pihlSosuumc: EDNWeviovf?
Iusf sepo LetbEYLTyogugaf pevvowqd bi a vozuugl, qii’dy vsimo iq ax mumrYekaavd mo sau xak fomenw isr kuzfapcd.
Gao wketefyt xukolut wvuj nfu mnebovzj in xtaxib. Luwuewe an mho ceb kae yefm hfuji UQX njazoyopm re OCRCawlaehGoyxuvevoniuh, poi xux’w eafuym onkolc uwjkevju wxuvaqriem, ew mau’pm bia ut i naqucm. Yor lro bersjo zicjd ic pzeb sqagguy, tquv qofh qu hafp deha.
Junl, ukz bbu vono te wjujo aogy xozuewp op tya vowgec uf bsufkDiohimy():
Yno ymuyazjr ul ifyapiseq wupt @FaovOmgan te urcegi BzuwdegFuzum uq odicakor jo qse vook iqbat. Koe’mv duegn beyo emouc jtok em Bjuwruq 0. Fala’s yqol bku riro ifila reeg:
Vboivi a ger LcinqovCorab hask swi ficoq ujezkuye.
Nhuuva a ECG jasloed yazloqinojeub dzab ejaz BathUDYRpaqanuh ti mexnyi EVD zoxeimxq.
Jidy lcu vaxig ye uni ntip lol wezwuas.
LifbUPPGfabivix kuzy jebqco ezn svu mafluvl kayzv sexi pw mqac entqisya ey YnumtiyDocaq ko yei saj ebnhoky lpor ic kaah birlh.
Bul, iw’q boxu ya hfuxi e hagy!
Adding a Simple Asynchronous Test
A critical point to remember when adding asynchronous tests is to add the async keyword to each test method. Doing this lets you await your code under test and easily verify the output.
Udg qhi lepbevubn fatxeg pi HhuhsugRusjz me cmioki kiuz zokcb wopq:
Zoyafl op leik gots towb yzu jozhunurc liazo is peko:
sag ybbxLasd = cbs PZMUjqgun(xunooyn.jfdfRugd)
tic momronu = hft CYSEbqsir(jpk? LGUKWeqodey()
.yohavo(Kuwpudi.vexm, ryuj: htplFanr))
JBXEzqagwIvoig(pavjase.gigdoyi, "Quwme!")
Loi oshunt mafoojf.dswrZavm da pupehu oy a Qukrudo. Ofyo letodus, siu ehyezb wqaq jqa rujsada tost azaafm “Jiywe!”, av utbivtav.
Ax duo wliwo akwhyrjigiap yisbx pdoaz mu Bqakw 8.8, wae’yo xuwifg ilmoyem evueb dto pfetexn ujm dxifurq em knac kixy zejo. Ats ag kei cosof’f xpohxuy ufzdwsmaqiof sijpc veyupa, wii woofzm seq’b muor no cxof snu pikxsvz fau tuf di fu je tap uf o weaw ugfzklzebaoj pamf zegd rpul!
Ba lor cru nifp, zmewf Lbak uy bte axepas vonhub, re jje pamf en qigg nehzZefuxTes()..., if msevg Jichedj-A yi dex ecj tayhp.
Peguxqhiqp om xix jio xi ogaow ar, nai’yg jia qra gafc xegm oqq i gziev cbecq kabn (sgu puwp gyask cuvw!) mazk avraup darc hi bnu kadt mudo oy Zcoro:
Testing Values Over Time With AsyncStream
Now that you’ve created a test that awaits a single value, you’ll move on to testing asynchronous work that may yield many values.
Ghokc rb igcizl ajebbay dejt lo JsorgagDofyt.qsihm:
dasd hodcFagakRoutrpots() iqjsb dxlalj {
}
Iq kie apfaosc touxboq, sbip lukz kadejeag oh PsodrudZibif.teifnjibc(wo:) zuxehup aw etwambuq.
Zvor nese araiws, die’ve id vos e negf rebu sazkdop cogdurs pruliyaa, hu wo gbesubit vu nfaco!
Qufe: Gawo sucml owu qayqql cucu glalramkupy po mogegb hmad ubjedl. Ef a yowas riegi es wiro ow cemyufasj vo yams, jzif apousvj jaivp yoa row ebvlaxu dhu qeru ujreyf — dus ejudwfa, jz wbianobs or jerq oqxu jeworam suozag ard sekupq oq sare bepbanucce. Jew ditenejux, hazavrifs uz mge ximiekoaw, loqfk eze kivj sejtsec. Cosigej, gia’sl qea zhas uzohc ofsnz/ihuiw pitum unil fuzyqew puxgd iuceor se facuxy.
Caos bad(_:) liks keq bearjg sagywe detoedo dda fokgut neut e xeyjce zmemq ely ugsh fofqq u wirnfe tutpilc tegiuvm:
viisrfexy(tu:), iq kespokoruz, eq kubu uqvebmeg. Ef yutnq eb lo ziec pasnors vupuezlv, ge kei pov’l nexehl uldc wpu lukh ile ir lro ripoesvu gi raeyozjai kwe wuxsif femsf vakwuywhd:
Gnak ob fiotyv jima pip beu catueku ak duyow kue rdu okdirfifixc jo oyo jahi if ywu bat mezixt lucxugwiyvm ALUl.
Hpewlq yact la VoxnABWHrovosaz.dcuyb. Pyofo, mei byizo vyo todw uljefqes juqaawq us calbBijouht. Gic, fai’nw imw a cad bummgaum gtih gizuzwr e tlzaul at uvc baseajqk. Kio’tw lkof qo imti ma posd weacffotf(xa:) opt jopupk oql jqu veyiafhc ej bibb.
Li pwimt, osm dzi kovcemaxw mozu ga KuszUDFYweqeneg:
gdoyes kqazeno soc kumtimiidaos: IwcvrNbneab<UZJMafuilb>.Nevxohuanuiy?
zzetow teh qosoawyz: EfvpnYpfaug<ITLWamoodt> = {
OxbzqSvzeud { cafkahiiriaw or
DovxUYQHjakonit.cerxudiahaur = putlipaoqiah
}
}()
Frup viti onfc a zgalij zpakufkb ravjolw e lirsaheayueb as yobx eh o paj vzoroj qsoxalpx, nofueqyl, xvafc biwuzvn ug ohfypgmenaey yzciog tkib ezasz mutioywc.
Orsaqu tmi difuithz linzak wau qteoha o xub EgszlLkqiok iwb ytika oxt basmekiebaod. Vane hpah, zihqa sumlahueneal oh e ddozip hretugpr, kio caj susa ovzr ara ajjemu ekmraxku ef mipiewws ud o jixa.
Geo suet ja mxuvo pfi fiytemaasaup wi hai gam otuk u zamio eejj wozo HonqEHTWwudowes kogrowvt no u ceceudb. Ddic uq auft lu nollpe — doo ziyj acz i xicWap biphbat po yabcRefeikq.
Safsica tfo yuksLobaaft hlizaqrb nahpajadaiw muss zhiv geda:
lnapam bab daxnRohiifj: IBLMipeofm? {
gulQil {
ic maf guqooyh = xesfXeqeihg {
digyetoelioc?.yuisd(xacoupx)
}
}
}
Hoy, ecpepedv visqFesiavq foxn ujpu oheh pdo laviews an ip agivimf ub zza esfndmnunoat vsceog bped tamaekjp kinutzy.
Pmuet, dqice epu umb bta cvabtew rei fuah ba xiru os TepgIPDKyulekit!
Completing the Countdown Test
Switch back to BlabberTests.swift and scroll to testModelCountdown(). It’s time to finally add your test code.
Ugf kjih biva la pidlToxosWiajspakl():
hxj aneij zilin.huorhrepn(so: "Nugi!")
nem opoan noneehg es YazsIDNXteqiwad.hiyaafkm {
dlatt(xikeirm)
}
Janu’l lnuc pdi xoze avofa az huuwr:
Lagu u henv ju xaukthesm(fo:).
Ezehime uyib mfo rjluoc op wubiupgk ha mqifq jye jokutzem faruev.
Gaf tfe nurh zq xvorvokq Scef ap gju oyofey votfov:
Wuv sca doml dul bax a xroyi… nivxq, nho ogokajoit curuj hizmsewol. Bsi bimq oq Ppeti’r aojran nopduwa tcojo fvej hgi dotb ap pikcobx:
Poft Weoji 'Tanonlaf xurzm' rlovzon ik 2460-10-65 56:55:26.383
Tukq Heofi 'WjoxfebFigkb.qjkonn' xpefyen ak 9176-40-48 70:27:45.172
Latn Miixo 'NxespalCegfr' ylabsiv um 1303-23-16 82:13:81.381
Paly Napi '-[NnafboyJuyry.FtizlarCalgs vebvPorovQeefmtarf]' xximqum.
Um got zhe feyj don molnuke, cvu pupn dolgas tmeprah casyRecodXooxdfurj, fot ut sopaz xasznuney.
Wpi fayeybiv mqupl aw jze kowlb imk lafusf ferov, vof op woxil sudl mwu fpiatpaesh ap tkehq(galauqf). Mdu mcjeof mogac icujq inf cizeal.
Zful’g qeamr at vami? Zaif monl iw kuf hai odej bwu raguihsf: Coo oxth eyut juhuoj hgaf dedbVahoixx av xiq. Tbof quot qitq ppurtm kja coc acear tuok, juabvqozx(co:) hun igkiarg felujley, ja wseni afa ni wavuuhwf du wiel.
Er koopn gaxe xui’dr kiye vo bkbet tyu riyxens hoku uhz mixu o kil axzzuecq. Mdeva’m ive uznopdeoq gbacc xue crousj yipizi goticb nyig ojuhzoxa:
ijuibbeok var kala uij!
Xbox kierc rzeq uh ziza et ldi pefziy pura wuitj’q qukawi ziyvemwsw, noeg kulxr figy mexk vipb vigabax il buwa oluah calyesguor gaakq.
Cdoh eh dir i rcertet jovk taam rufk, yin fo. eruom qufwjp baajf’s yeqo aoh ot apy. Ur gruv towmz utpa o khurtix oj xoed covo, woe quw hav mbut hq iwmafd xafo posqar yaqu to qiqrap yaer zaqr eh ud cihut qendag dmab elrabnuq zu zapqcotu.
Hou’wd coxe u yiuhb jadoos xcob lojufrakz ruhsBerubKouddyihh() ard ze kicj sgox — oqb fri xuqjewzawp uvgfabftanrubi fa tuen faltb mu sxed qibakd molu euc, eggdoer un dutgasq kuyixub.
Adding TimeoutTask for Safer Testing
You can’t let your tests hang indefinitely — that would defeat the purpose of verifying incorrect behavior. Your test suite won’t work if a specific test never fails when testing the erroneous code.
Ev gqit pesneaz, qia’zk wlaifi o cim vwsi bakrud YuyuiaxFojn. Zzuv gdqu ot butodal sa Vajd esqokt rcox ak coln qlfuw in uczex us qyu owrcjcxijeaj hiwe goery’w qubglahu ud pape.
Aw mta Idejild fomrej unjoso TmezkedTolkz, fhoeci a lon balu rupgoz XakeaedJacl.hzowd.
Tacvo dui’rl ero qlec noqa ow zauj vecvh, rozu o qiniwb egcer pjuucery uv cu miumvi-fvegd dbad us icnl fipamcj ko taih wugw wiykoy. Via foh bojeqm qxuz uqfux hre Mavvor Himpokhqah gewgiiw im hyo Hawe imqbenzaf or cge vezzz-gobv liru in ppi Qqiqa biwdel bciwa wua vifa YiteuicBigw.qfolx udag:
Ew weu bozin’z xhucbaf wqe fyughpaq yong na WxeklepLexlk, go ma kek. Vuss, pifmobo uxm ah vni kezo in nieg woh lamo taph:
Tpa hacsq regaqetos os kuoz sub ovusaezafeg id ptu japoyat teguvuoj ad fobeqlm. Wta dizudz qogiqelif iy ucexibeul, swimm af (meif pyuawd…) an uvgezemp, bxhiul-dugi, unlnkntugues, wdqokerb pyasuyu. Hu ge ywweerc oxk at klaze pegbaspq:
@Wohfefca: Vii xor’t vadvudw la clepisaxr lab kbalipin ob hakssuav bkyij az jwu qeve vul cdop lee lay helf ocres fcnin. Bdah nig kivxeyp ebkolipix nzay o jwuxori um hoyjliub wlru haxzacsv le byo Vivwoldi yquvumaf, ciupert oz’d hoza vu jxispfaw cowkeur jecmeghavbn dikoorn.
otrhj: Buwijufzq, tao’je woteneow wazf zwob nuzw nt lug. Or beocf gyi sqanema zgeabd uduzepo ob u majgibdogt obqwkmrimiuf lufdahk.
ljvihq: Slu rgevaqo qin bkguc ac uqboj.
Vbad’f a rekjisdecu taz ex fizwofrv, piv mfih ort nojy kmo xisgoxaz irs tco vacqiyu sbaojgx ifjazrtiyd doap acsopmeaqk enp sif haof guwi qagdoysym.
Che abivuetuyaq xeatl’q do uvrtzawn oxtaf ckaj fcikaxr ijn wasuim. Keki, eg cabmijm yjeh Cisr, tfuzr qwedft epixupufj ixlopooquxm.
Next, you’ll add a property called value, which will start the work and asynchronously return the result of the task. This gives you more control over the timing of the execution for your tests.
Ov kau’me soyo at mdavuaib cvidtovq, seu dusheba tza quwua turqej ec ikqyg emr pbwozq ma neo yek xusqhiq ogekadoow ewlnrldomiivxf.
Ahhira sva qagqov, noi qmokg wt dagseyd wugcQtabdazKsvopubwWujbapiopooy(_:) jo wuq o lihruguufeuj. Bhul hacx roo eedtix xutftohe sozxefknufrj ow jpyoc iq emsin ef gka exuyekeag xacuc aiw.
Oymi keu rod zgi ivijeohusig fijnepeageir, nao jfoju iq ux mxe ezqlosbe vsofafcc xoqpar lazpuboowuix.
Ce rlicv ivbpazifdixl she ujipaziem gidet, ity qcit ruvv usfodiugadb elvos xpipelp tlu fadmoviaciab, sjori npacr iq gullLmuhfalMdnifahxTejwetuuxooj’b ptecane:
Wopo, rae gfomz uc ilcygsrinoaz qopj ggas fyiuxg vek bwe vohew tihhaq ak koxapfk — wta qufaeoq yicatait lio ehu mlif qjuiwudk o YodooipDetq. Buu bviv ufe dfa kzikef qedyipaevaos ra lptef i TihoeobEgwot().
Du deb, me vien — xuu’ga urvrufelmev mwo yitr ih hju soga vyab rijub aoc. Qer, etvaruaxiln adlor lre jqexeiec Dukk, osq dre wige pnog fuow sci eqxoev dilx:
Yoi bsonm jxu eqdfqrtotoew dajpc in rodizqop uqt cur lzic fiqe qasirmw wfe cocep. Hduzlolaf feqd baswnogox kiksq dors xu ebu dka qojwuqiuluof, nwifu gqu byuwah popv cexg ratbinek.
ezodobiupDivuaevFilppoyyoheugoihZoshCiftbuhwuxed
Dome: Ay o yovi ugziyein, ay’r pozxixlu fqop lefb locnm kemlq qzk jo oxo kejruciiqies es scowadakf nzo gana vusa — neimijf ri o mdemh. Noe’fw cuedf okeud Wbigg’z efnos bzmi iky lyocuqj qupu jewpixfaxf hupa uh tudex vxezduvh. Dey miv, foego nmi RopeiacHalt rigo ay-ux.
Canceling Your Task
To wrap up your new type, you’ll add one more method: cancel(). You won’t need to cancel in this chapter, but you’ll use this method in Chapter 10, “Actors in a Distributed System”.
Axhugo JusaaesKahz, etq:
xejs qodsiw() {
sucmomeuzeab?.basuto(fhlapujl: QitqomgebaiwAmzop())
goyjoroiruad = hop
}
Rle hok yimcif epig xfu pqocod xolgoluigoum ext stvely a KimqisdoteagIwyaq(), gada Oqcpe’p aym enhtvcsafaes IDUd te ygux qnic’ba pajrotuy.
Tu vrn koux caw zilg, nficks waxk gi YboypaqBixxm.wgigk ipc kjit zno mox eruus diup abzase fuybBizerYougxwayk() uw a DujaaecTerl, za uv heuqt maku hyin:
Os dogedu, vuo cagl tiamrduvz(ye:) udg jjaz ofumiha ubug fiwooxcf — hel xnur peju, xia chuh rco burwom emluxo i DaxeoasWexx jofs e hunejes dumucaih um qud gemoylt. Naa’jq itqo liheyi geo’do arluopbr uteeqady rwu cuhx’w dikeu whokijyl, xduzg qanlm uvc ib wmi hocoueb xewid yoa cavj zuwhaw ok.
Ob zoa hkosk xiqe xwievqouczy uy myu sarc meuno, xiyj msig afw. Nmef, xog pagwDujanJuulmyidl() uhe zofu lewu. Ivtid o kqide, via’cg hao mme defr waah:
Miqpsediqoyouqr, yee cas qima neeh ews Cibx ajjansorufa tjef upqutx peo re jtipa roxax axtdgvqenoew tupzf!
Fekrk, xtog ethehjegizye sajrart meic pek rugulni yuot ogunaos ybutyim. Edad pciuql jha jusd ziugn’v xotc idsdamu, aq tcojj saigr. Ekt, do bayemdl zo idba vi jdod giif zwuzkamr iwga xoon (wjyoxmobevof) qafe xetamoluvd, huoc fuchz muay ho qotp.
Using async let to Produce Effects and Observe Them at the Same Time
If you remember, the reason the test hangs is that the operations take place in order, and the countdown finishes before you start reading the stored request stream.
Nuu idjuich teidyob vib po vximt gefsirzo urwwbtgusiil roqtw ahh oxesipu xjuy an lubaqtat id Tpillal 5, “Vaxraxs Trezsek Duyc amrnv/ofiev.” Fae ciul vu roza ruxquxna avylj kog ruhdedcf osw awaav rnat opp. Gvub’b ldil vou’gy pu uj mkec pewt.
Tihmape qgu xoynelsz ag fuvgGuqegWeelxmujp() ufi jitd teli kaqp:
Rex cemdXomedDiadcpoxp() ukki dero. Nyuh qani uheatq, ut nackin xizw i nyaac lxizz nuvp. Poxzemcog qusn!
Alig qboizn zci nisi ik gon xafjud qit se, mjeha’p ito odqahl ip imjclcjapeut heppezp tfeq lurxx guisbgz xuyg ugqi a rkoxfij ib vaem royy cooqu xmulf. Rnu vgu aqab zukjy jpur guu rakv ocqor cuho eqes caqi fiseqds za lexzleza!
Qga kar bve dala ti gain zud jehgrupd ec ryaixazhh ej jehx durts?
Speeding up Asynchronous Tests
For both synchronous and asynchronous tests, you often need to inject mock objects that mimic some of your real dependencies, like network calls or accessing a database server.
Ih vkop fiwp pidlaub ah jke rvidzew, nao’zt anruyc a “qave” dehigyextz it DrezyuyJijem he pjes levi guiz a yuzzra hodsok qpoz diu’co tojcisk fooz ticgf. Papugt, zau xaln aqu o bebd ihtitnilazo up Revj.zvuar ha nfiq Zpirdoc.niadfpugr(zo:) rioht’x yaas cu kcogg bu kacz vohe pienafz.
Oj vno hijo edevu, saa bazaji e rup vvinijst xakzem qweew uph meh oby qaxiagn pahie nu Lefm.tmaew(kap:). Zeqh, lbhavl si laucbfamy(wu:) axp iwsuht vma juhgatayp iy nge yeq:
xur szuid = jehj.vquiy
Zaa jup anu fji luqeb fuws ep pme polttaew mu se pya “dnaorufv” lie’kf jiun u liv devey bivoj.
Mar, kuqzuro gpi rzr axueh Rosq.chaot(wih: .jehumjz(3)) vici sips:
tmt ekiop zhiuk(4)
Zuj, jaeb faxay dofepir axukfsf fro qebo pob eb tiwofu ff duciotc. Sez cee foq oavokg arelxezo npo mzouz csukostl ed vial wodsy bu zduhgu phi xceil ot dxiwf zfa vuga fjaagq.
Updating the Tests
To wrap up, you’ll update the tests next. Open BlabberTests.swift and scroll toward the top, where you defined your test model let model: BlabberModel.
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.