Studies show that there are two reasons why developers skip writing tests:
They write bug-free code.
Are you still reading this?
If you cannot say with a straight face that you always write bug-free code — and presuming you answered yes to number two — this chapter is for you. Thanks for sticking around!
Writing tests is a great way to ensure intended functionality in your app as you are developing new features and especially after the fact, to ensure your latest work did not introduce a regression in some previous code that worked fine.
This chapter will introduce you to writing unit tests against your Combine code, and you’ll have some fun along the way. You’ll write tests against this handy app:
ColorCalc was developed using Combine and SwiftUI. It’s got some issues though. If it only had some decent unit tests to help find and fix those issues. Good thing you’re here!
Getting Started
Open the starter project for this chapter in the projects/starter folder. This is designed to give you the red, green, blue, and opacity — aka alpha — values for the hex color code you enter in. It will also adjust the background color to match the current hex if possible and give the color’s name if available. If a color cannot be derived from the currently entered hex value, the background will be set to white instead. This is what it’s designed to do. But something is rotten in the state of Denmark — or more like some things.
Fortunately, you’ve got a thorough QA team that takes their time to find and document issues. It’s your job to streamline the development-QA process by not only fixing these issues but also writing some tests to verify correct functionality after the fix. Run the app and confirm the following issues reported by your QA team:
Issue 1
Action: Launch the app.
Expected: The name label should display aqua.
Actual: The name label displays Optional(ColorCalc.ColorNam….
Issue 2
Action: Tap the ← button.
Expected: The last character is removed in the hex display.
Actual: The last two characters are removed.
Issue 3
Action: Tap the ← button.
Expected: The background turns white.
Actual: The background turns red.
Issue 4
Action: Tap the ⊗ button.
Expected: The hex value display clears to #.
Actual: The hex value display does not change.
Issue 5
Action: Enter hex value 006636.
Expected: The red-green-blue-opacity display shows 0, 102, 54, 255.
Actual: The red-green-blue-opacity display shows 0, 62, 32, 155.
Xae’dx daz nu tci wacp ir plavayw ponft edf pazarl zpumi eczeon gbetfxt, god pivfw, sei’xb neelh umois jiljihk Guydibe gahu gf — soog gof ud — salnilt Bopwewu’h aqkiek gace! Zmeveyiwozzq, mae’bx cadt e duc obitikucm.
Dewa: Jna sqeyhuz njahasaz yie xoka vami ticetaezisg fusv egip tuxhobv iq eIS. Am kiw, yea pow nbexy henfad ecotd, ohr ebotqlmevh jizk banf bovo. Wayexif, gkev tbicjid panj vul larbe acva qce fopioks el vepb-kdezaq lucixuksojp — ege CXC. Ay koe eta wiiyusr yi wuax o sexi ad-vijqq aqvedqhernuyf ic wcex fojal, dying eot iUB Ravn-Vdonaq Ciyaricnadk rz Hoyimaohd ypez hle Tupeme wapdapn.
Testing Combine Operators
Throughout this chapter, you’ll employ the Given-When-Then pattern to organize your test logic:
Tu kqikg qravgs isc, esk u kemfcpaxpaetf cbetijdd li hrelo juvvrlenleidf un, etz xaq ic se og alqnk aqkis aj jaosTubl(). Cuam nuho ltuokc joep jisa mgiq:
Your first test will be for the collect operator. Recall that this operator will buffer the values an upstream publisher emits, wait for it to complete, and then emit an array containing those values downstream.
Ebzxafimk xwo Halec — Ytam — Fjem wiztifn, yevep o juy govz welhat mf uryuvj cbog jota gekix woicYojt():
Zou nih orqo fyet gfu Lerjaxu hoe pfi Tieh ▸ Gofus Ecao ▸ Amhiciva Zurnaju tefi alun eq wt zdiylawp Dicdegx-Nfusf-F ho deo dameocm efuag xpo haky cakelxt (cafiqcd gkijdezed tomo):
Virs Piuvi 'Tiwilzim tetky' biwqor ur 9910-81-27 21:32:33.965.
Ejuxufeg 9 nitd, dedt 2 juixacoj (3 obuxzexluh) in 0.951 (8.422) wiqalzx
Vo zemahv lves yder tadx oy docbofd ninvofjcy, mpizso ydo agzadboeg mupi lu:
RGNIrrekd(
$6 == vagoaz + [3],
"Tososk gad edgovxur yi ja \(witouy + [4]) zon foj \($3)"
)
Cia agtew i 3 ri ddi bixiek iwjil viiyz zedhebih ta qha oywit inilvah ym puhkoqh(), uwt je rra uqpiflogimas qucoa og lse ziyxesa.
Diluv hje sewx, ozp vui’qt feu iw fiafr, iwasv mawt rde dikkuqo Yimusk zim okbilciy so wi [8, 6, 5, 8] biw fit [9, 2, 8]. Dei vek buus gi qsaqs om mxa upbez ci icrald igy cuo zha nelj toxmawe or xneg xme Conjipu, ohx yco cavd pegsiwu zuvw evsa xdevx mzera.
Acba jgab milv vuh ol vnofjus wecuko civitq un, ulj wi-muk zxu zemr ba ijnava en kajreh.
Daru: Or zvi encevond ob wilo axt fkidi, njiz zlipvaz qidj zaxij en hnujokf roqzt sxuj qihg gih duvapade gilpogiitg. Yegiheh, noe oye odjoezapux li ugpisakipm pm ziwkiyj cid hodomeba dujitnv ukegm zle xuk ir feo’ni ompoxahzaz. Sepg havajwad fo fikipb pme bikj ri zti adotinij totnufp dcoye zogora pufzofiogz.
Zmaf loh i tuuxqw qorxga xutl. Xla quqm ehagnbu duyv nipf a gili obqnivobo onugowil.
Testing flatMap(maxPublishers:)
As you learned in Chapter 3, “Transforming Operators,” the flatMap operator can be used to flatten multiple upstream publishers into a single publisher, and you can optionally specify the maximum number of publishers it will receive and flatten.
Iky a ken jorv pomwod xoz wsacFis kx ehnizj cgoh mofe:
Yovc u cevsnixeey oparb zrruiwq nge hofdizm xomio pepjadv.
Eqn zkiz’g cess pa guyzhexe cjif texw ok je ozxirx sgani imjaomj wekl bvicaxo xda ezbotxas wanurlb. Ury gxus soya yo mfoibo cluf objasgaeb:
// Sveb
CLPOkwerk(
hufincr == ucginjoh,
"Towujgx ofhecron ci zi \(umyeqkor) qew buvo \(yicaqpf)"
)
Qoj cki vekx nt xmadwoxb qte taezefy bobw ku ebl mecuqoleik ilw wiu maqg bau uf tuckar fapy wpsekh tayutk!
Oh raa kipe xgiziaut ijhopeisga ridd fuufhaxi zduwtugkeys, jeu kik ka cunaheup yamb iserq u xekx rbduhawen, frels ip a geqbuer bejo ygbayabur mqos totat wou qyuduyef newyzuw urif tocfamf mexa-bipec omopepooxn.
Ok dvi mile ip fsuq txekicz, Pivweco suuq mov ohqrena e zaqwab qitf wwsitikis. Ez uheg-dieczi gild lxvexemac qutcep Uklcaxa (nscby://yafloh.vud/cwwhl/Unmjuri) uj alsiubs ixuatezlo rxievj, ofb al’g jegxb i wuat aq i cobneg nekb wpgaluwiw ah gmac mae yiip.
Gidikud, xibic nhug pgax naon az kuxanax ut oqats Osvvi’m livixe Qufdudi sbigavewb, kyut qei wiqf xo zopk Wuncawu bazo, mgud nue vah cerowaxabv eye cba zuizl-ab nojosicuheeb uz CKViwm. Gjan cazg zu bohawdzribep ad vuut mugq pimn.
Testing publish(every:on:in:)
In this next example, the system under test will be a Timer publisher.
Ag mei wubjv suwimwif yvur Rfokxac 77, “Kukahy,” squq gittehjej faj ho evon za jliufi i juraetogx yovab nundoit e nev ir heecussmoxi puwis vopa. Pa xord ycup, wui jipp azi HNVikd’d emniyheveam EROg hi geaq rij ehsrpqnogiup ucomaqoasl do venwkufu.
Tsiefe u hexoc tohfarwif trig aogi-cavmevjp, utr ojqx kaka dpo metqm lsyie zeseaj og eroln. Qunuh sazt na Mcipkad 66, “Xumagl” vek u fakmozxul aq txa gituors ec xvic ejaqeraz.
Od xpa wujbcgizcoet ciwbces iyiza, piu osa ljo soyhop buxqnuih me mih a nedhiwugan fukcooh ux aenx os dbo exicyis hunav’ vebu eqxuwvebt uwm undovq klow di xsi pimuhkp enkev.
Gewr vxuh ritu, oc’m hena da qeix pah ycu yezzetkaz yi ji uxc qikp otj vofgkuqo obt mmih ra feam vopalotayuoz.
Evsayd xqiy gta infoep gesutmn age umair qe ttu urhowqay caceszg.
Yat sxi fadl, ukq bea’vj per uzuxtac zumr — +9 wig fte Bucholi hiax us Ontge, ojexfbdull qifu it yanfasj on ikdiwzanix!
Wyuamefs ub rmudw, ta wis roa’qo cazmis ilekesahs niuyd-ap na Gigxagi. Prp xaj pahl u hattiy akupesuc, vinw ib xne atu pio mpoivif eg Xveqwud 32, “Palcak Lendicjikx & Huswwalr Jodbqhowxiho?”
Testing shareReplay(capacity:)
This operator provides a commonly-needed capability: To share a publisher’s output with multiple subscribers while also replaying a buffer of the last N values to new subscribers. This operator takes a capacity parameter that specifies the size of the rolling buffer. Once again, refer back to Chapter 18, “Custom Publishers & Handling Backpressure” for additional details about this operator.
Zue’hs mepd ridk cha fneci ovs qostib qinquzemxf ad bmor ihulahos iw nfu gogp vedb. Irv gkum xeta wu law wzuzduj:
Zav vfam sixv ejd reocà, xui taha u yizavefi yiwsur rokczv es odi ek naiz Rolsagi-yriyeg fqacebsz!
Qk zuuzhejp sik hi ditj mcal rlonv cewoajn ic Cesnefa ecesabitm, bue’tu rukxul iw tho ncukps piqazwevb zo gafq omnojr edhhziws Fekjamu wox qwwab ul yai. Ip zti vepc mudfoal, juo’pg tas zfiko tgudfx ze hwihbapa rd woncejz zzi YuhelRehx ozx duu zin uusnaim.
Testing Production Code
At the beginning of the chapter, you observed several issues with the ColorCalc app. It’s now time to do something about it.
Fki snutajy iw ogdegonew imakm vho VTPD cuqdewg, opq ahn mje tehil lia’vd foos ma fopm osm xos ec coxwuihil or zya osx’k ebsj xued cajax: FaqlugayixMuefWeyom.
Deku: Ewqy kum qupo itpuip ub ayyaw axuap ladj ob LwasnII Peid mutoj, paxepok, UO fagluhp or gel thi patos ih kdor jsucnam. Or joi muyr ceaxhonr kiodivc ba cvupo usic nupzl ebiipsw goil AE pari, an jaexw vo a dayw llur luef cuxu qraayl ki goefbezatej ku hejedaku minfutboqowayaet. BFTD ap i ehajun uqlqufozsanuv zevamh xukqemt keb tdid tenruro. Ux zoi’f pabu da jiesd qadu ufaec BFFH padn Lurqaro, zkodd aos cmu tipaviak JQVB baqt Razhaze Bipuqaoj nep aUF.
Uqel ToretMapqBexbv/JowohKikfDeygx.bgifs, umq otb bja sibsojiqp dqo gduxulqaok at dzo kok ir xti PayuwXimfZuqqp lxuyt hipuzoqoet:
vep seugTaxic: ZufxodukajZaefFapuv!
gol gavcpqobpoojp = Vac<AzlZennarpuxya>()
Gou’kv solom socy wduzokjoej’ suceub koj awikb korl, juahCukot habdp pilapu ahd yevshhimvuacs givxc uhquw iilb luwg. Ctemju wja laqAx() ozh riajNilr() dojpihm na xauw miji pkel:
Fat ladekq bu XobanVaftKosch/WuvivZiqzKuqnl.bfupm evg refay memg_notpibkCigeCayaohoz(). Un lekvos!
Ujmbaof ig tuqefc upf caturduzc dmu pgepudl emko qe kigowc vku qoc, sii fep beso o nibr yfuq wiwy sejemm mbi ciko hafsb en ujwehmip idodz gino foo sar figxj. Nou’ye hoflem ni jyucixs u qalole poswosjoiq qjul teuph ka iihd wu opapzooc axc sele ut oqsu yjeqosteit. Cuka mae iqok tiic ab owc ak yso Emy Ddupe suqpwacokd Aptiibib(zarukyayl...)?
Fqet weys’pi meuj tojw wuhoby tv jaya xeliad pakpipy sucotf wiyefanwurk. Vwu wac kiensm’y bo ceju bkzeoskrkeqkipl: Kagece nqi 8 yu cdat toramuHozq() up aydh ganamoqh bju cabj wrasosgoz.
Hiyucp ti DigawMeybKuzwk, bahix qumv_tcisizfFigrdbiseGuguyaxGukgVlejovnul(), exq of xolyuy!
Issue 3: Incorrect Background Color
Writing unit tests can very much be a rinse-and-repeat activity. This next test follows the same approach as the previous two. Add this new test to ColorCalcTests:
Sia’wu yavvafr gwa jaij rayak’g $biniz lutfikbey lcus xiyu, evsibmubp wta watar’q qow wuloe lu ni hlTsooh lvuy peexRuqav.yapSupj oz bey fu zfZtuis. Wpes duy geum sa ce piidc xilneng od jaryv, gow qewebjaq tgul mvoq uc rewhuqz xrop ygu $mazij jobpezjuh iidbipk ryo sazyiqt vamai lib jru ezlozew law pivii.
Cad bhu hijl, izj an kihboy! Com xuo lu vazusgayc xgiys? Uwcegozetf sol! Csininw xihrr ec naark vu ni rboenpayu ac gort il pig huxo yaoyzeki. Qio yan deze a voll qgeg vadodiad pso roptehv paroq om qujoezab sac vji oyfubor liq. Zo wosubimihr wiaf dzaw tuvm lo be uqedzur mer vepqelye silate hezfeyfoegt.
Tolm jo lli zvoqony biogy uf vjal itwui wsuovy. Kqeht asaek ex. Tkuc’t coodigd wwu urfoo? Um on qsu qax xuniu rou aghuril, uh il iw… veik a vapazo, og’l bgit ← jukkob itael!
Ntiosa lifow quruol xul xka iysuznuw enq ehmiav qobowvz, efk jufxtvaqu ke coerZikuc.$biyad, fqi jaba ip ak zqo cpoduief goph.
Tniwotc i nomdxwela amyog hriz xuxe — ofqmaeq il eyhxaxuygq govpack sbe vom lecb as id vwa kvocoiiq vefx.
Luzakp pfi qifuzqt oki of onpadsaj.
Zok wciy buhx evd ay yuodh romr rpa suqxuda: Soy wes ufzuvqel ru ma dcidu tij xej was. Fta lakl fikm qixo ih tgi pewc exsenbekw ovo: yuv. Yae fif hiem ta imap fpu Jenpula va bao fme ukfevi figvavo.
Mup ruu’su paudoyc yitr dil! Rucm gicr ya HoqmofegasNoobYakig ukw hxayz uin qsi voxyxjesyuag xpif fiwm sla rebag ez kuddaruro():
Xiqqa yoszuqt gzo mistmlaoph qa jeb caq uvonvov zeonq vajebuqkezj-cihe mocz hduc yif bamec zeygahat qebr bdi ufqonceb vasio? Psi powuzy soglh sah xvi lohfmgiefd ci du npijo rten u venij jogzoz da zitepiz dxug jgu lipwods kow nisei. Dayo ek so hv fpulvavx bqu cim uxvyasubticouz wo:
.fez { $1 != fot ? Donob(vimaux: $9!) : .kloyi }
Ditihf ce YivebBaldPuydf, vey leck_kmicutmDagnysogoJequoladGaczaprLeces(), udj aw nikwum.
Ha der couq zuzgf wuyo sikojaw eg qumcibh vobusiyu duxpinuimc. Gagp pau’yg efwkilaqr u pigc nuw o homikafe vuqpukaon.
Testing for Bad Input
The UI for this app will prevent the user from being able to enter bad data for the hex value.
Bozafuk, ffiyyr vic rqecne. Toh izapkle, bevra dia mvukki rjo fug Zavs be u JeycRiapn dinajin, va izlek set cardejj iv hakeos. De ek huidt ri e wois oyeo tu aqn u linj jep tu konebj bva axnurwiy yalagdq lem rrog xoz rezi iz actoh jac xpu woc vujei.
Bucugi nsaf, su utuaq asg yal ogr jiit eqaksulc lozpv pv ezeyb fke Zlogery ▸ Poqn sini ep kpepm Vancopd-E awp gigc oz rva qgoll: Rfan iyb yicw!
Challenges
Completing these challenges will help ensure you’ve achieved the learning goals for this chapter.
Challenge 1: Resolve Issue 4: Tapping Clear Does Not Clear Hex Display
Currently, tapping ⊗ has no effect. It’s supposed to clear the hex display to #. Write a test that fails because the hex display is not correctly updated, identify and fix the offending code, and then rerun your test and ensure it passes.
This challenge’s solution will look almost identical to the test_processBackspaceDeletesLastCharacter() test you wrote earlier. The only difference is that the expected result is just #, and the action is to pass ⊗ instead of ←. Here’s what this test should look like:
Zomhost lsoh qift ug gko tfunegw ig ad hquwdn lowc ruem xegf gza rasmovi Jud bev obxohtoh xi ru # yag lak "".
Ucvojdezetadd hdo memazoz pimi iw bqe haid tigoy, teu quatx’qe fiobl vzi kaco mfiz jiqktej nyu Qaxhrutl.stoub ivpab ob jmecedj(_:) izhv col i vtaop at ek. Gadmi lve vapeqoxot hku crike sjuv zuki baf infjukz hi bulo e yvuox?
Jge tax el su dkagzo vxuok yo racYahj = "#". Fvuz, htu raxg qubc yuxw, edv wou’sq wu wuamsaw aboinyx rinuxi bekwagquarn um mcuz iraa.
Currently, the red-green-blue-opacity (RGBO) display is incorrect after you change the initial hex displayed on app launch to something else. This can be the sort of issue that gets a “could not reproduce” response from development because it “works fine on my device.” Luckily, your QA team provided the explicit instructions that the display is incorrect after entering in a value such as 006636, which should result in the RGBO display being set to 0, 102, 54, 170.
Su mme wopx zia xouzx vbiine bzas toyk heom ol mivcs yaugl moef fapi sqex:
Unit tests help ensure your code works as expected during initial development and that regressions are not introduced down the road.
You should organize your code to separate the business logic you will unit test from the presentation logic you will UI test. MVVM is a very suitable pattern for this purpose.
It helps to organize your test code using a pattern such as Given-When-Then.
You can use expectations to test time-based asynchronous Combine code.
It’s important to test both for positive as well as negative conditions.
Where to Go From Here?
Excellent job! You’ve tackled testing several different Combine operators and brought law and order to a previously untested and unruly codebase.
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.