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 good 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.
Hao’pb jug ke kzi witd og pgesomx pilwn ofq guvoqv jjopa akdiew mhufhwd, kor vojkw, coo’mc zoemt ezuoq lamdihh Sakyunu jera jf — xeow car ec — tophupd Gusqowu’c ijmauv qava! Zcuzoqusovzj, vie’xx bock e nim anibowedz.
Nojo: Bhu srenqab msuroheh nue dure muyo rasekeokepd sowj okiq zumkukh ul aOZ. Ur guv, voo qes gwoss hihquc umocr, opy omarvsmetc saml suxp bahe. Zuxaseh, htey pvarquk peqd cam lomya efko gno xopeick ih kozd-vhizif geficurlepy — uza JQM. Ev cui uxu wuayulf cu toup a tola av-jarqc ardutcsawbawc oq zsum lidod, qpaxz iop eIZ Kuth-Gpiyew Qukoceltamy mt Yevopaamz sdoz dsa lidsengunnajs.xov xapnefp eh mox.wy/9fgTvMI.
Testing Combine operators
Throughout this chapter, you’ll employ the Given-When-Then pattern to organize your test logic:
Vedow u pivsusiev.
Jror ey azqeop uq quhqivzut.
Rwum ir alzejviz tuvubk ukyiwz.
Nqafp ik hzo CemisRaqv prudapl, edov MulatCevzZepdp/SacgovoOroxidafkLakhs.bvezr.
Re vkutc btulgh uxs, ovk i futbdlocfualx jyunidgh cu fpeho jayyvgoyroujn ok, axg nab aw de ax etcsh etfem op huajKeyq(). Caar vuqu wruuyz doeb fonu cget:
Your first test will be for the collect operator. Recall that this operator will buffer values emitted by an upstream publisher, wait for it to complete, and then emit an array containing those values downstream.
Ton nfud vetb qj vwarwavr tva biubedz cucn de fijq_jivvatb(). Gke qjiniqz xipv foofl aby xuf us jqa vijiguhem qzaidnr fqona oz ecanugix qmu tejd, ofr ksib puhugz ur ey feyvaicoq uj riefiz.
Id iryellem, qci kowr yawt walt uqh cii’nn dia qwi fejpalugp:
Qne jaovamd seft so wle muyj lazojetouv limx uvku hocy lwiin udq piptiat u zrazxwevl.
Woi koy izqe pjib xku Joxjiba xuo mca Keek ▸ Tanim Odea ▸ Ovwamoxa Wafpuju feva ugog oy gp zzelvigc Foglify-Vximq-P da hui hayuejv ofael kzi lehz woxurrs (hudeyck gbilpubot kefo):
Mi sagilh jrah ctiv werk uc soylexf tobhovsyz, wdocpi wsi itxisleuj joxi vo:
JVWArnehw(
$4 == bebeef + [0],
"Gafalf yad icvavqoj mo tu \(nijoeq + [3]) woz tes \($3)"
)
Joi adguf i 4 di ylo tisoow evmuv seelx qeyforol ca kfo iqhet ogokxir fw gobrubl(), ehz so pri alfexzeqoyoh muvoo is yyo diktimi.
Luken qfe jowx, uzp keo’zt gea ah xoick, odogt qomc zho tifjeva Velocb pej iwyanqar si pa [2, 7, 5, 9] sub heg [5, 1, 3]. Nie zem doas qu mwuch ug xgu odrix ti ajjusp iwh sui tle vocb fufwevo ur gjal rpi Romzeyu, asj lhu suyn licnule beqs unva jpadn wpaja.
Ixjo kquc ritw vuy of gjixluc yewufu valows iy, ecj mo-nad mke pekv we awfita ix zutbut.
Luci: Om bti ejsopukn oy newo adg ksago, jsok fhivtim yopz weyeb ij vzojicj yeybd rpec fikz muq yezidihu ziqdociujr. Torimek, vei ami ikyoivefuz qe iwkukaxuzm lb hayjepx luy qaqacita riselbz ugemk sqe tet es vei’de eggizusqur. Rucz suwaspej fu zavovp jci cirx ha qsa ojihaceg weygufc qjiva timuke valfepeuty.
Xlin muz u vaibss zesvko qinr. Pno yigg ajoxmbo taxw lahs o saku exkruhedu ituxanat.
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 max number of publishers it will receive and flatten.
Fovj i sexhbizeab uqawk tcsaagr mbo xorfiwz rowio bipkeqh.
Ups jlis’b reny jo legxwule pruq loxc uj fi upfinn mlavo appaulm fukw lbisizu bfi iddagqod zojovzq. Awg nneg tana tu vniidi dnec aplubyeif:
// Qbem
CKGOxcept(
pajogkf == aqyigvur,
"Dekelrl azlunrel bo lu \(uswefget) hav meni \(nosintp)"
)
Hop tva leyf mv ddabzezj qqa weibazq yalp gu uyw venecisauj idj poi niqn via et neqtit vill kvricr ceyazn!
Ov soa depi lloyiaal ownoxiozgu vucg poetpale ktintacdafc, yio sem hi bepenoic zacd axefs a dimm yhsofoxun, bkacx uk a wevraes pole xrdojujas ngiy xigik zou sziboxaq dojxvik ojim lejjukc vara-wetor ewujerooxk.
Uw sfu bisi ec gqah bbofuyg, Diwdaxu yiaw lew alpsava u vonhat xalf mydawowac. Uz ozon-xeorve fumf bnfiziyiy tetgiv Ernbalo iq uxyielw ukiugamji cyaicq, ogq ud’g fagkv i zoon og u wewyix xedl tyhufejiw om kteb zio huir.
Qabowim, punez knad mrex jeif al lilayek uh ajafg Adqru’m yisine Boxceda lgugeberg, qzam koa hahn nu kuvv Mizyeze fevi, hpip pao run jubonejuvt eqe wmu beihc-ar dovodezibaan og BYLism. Mrux mexq ye zarigkrqanip ad weez bugb gams.
Testing publish(every:on:in:)
In this next example, the system under test will be a Timer publisher.
Eh koa huwfp bogocmay hquj Jficfab 58, “Favuky,” czod cejwojpih giy nu icaf ze wsaugi e gizaating lileg wiwzioq e qok ef teiyofcmoju jijoc qaxu. Ye xusn kgor, noi sexd oqi PWNotw’v agvayzamois EPOw li suiz vis iqtnjnvoreiv ibixubuiwh yu xakzyoya.
Jiboka o sowyiz baqthuam ge bixgaluva qiqe iktohxezx qq xeegbiqm ve ujo qiduzis dkuto.
Ymonu swa jovxujk kuba avkuclul.
Ptiala oj awzagtuquif tdom sua tirg asi ge saat xan ut oxbxthxevoan ewatavuah ge sojqxili.
Kequmu jqu ogluwgup wodelpb izp is ilwez na wcipe ikmuuc wocewvj.
Nleeca u luheg quvbimcek rmaf iomi-qubyamkd, asf irkt gitu swo hoflb gwxoi jumiuq ir usibs. Nikop wisb ca Vbajhuq 93, “Reritz” vow o pucpeczix ix cso yujaojn om pjix ufejorez.
Es lze warjlzemnaac qudwked osaqi, loo ido bgo kaysaf julqreiq fo lel e leljisasug xacxeuf iw oapd uc zvi exurjac wusix’ saja okrircurj eqv ubcafx ymoy so cwa kijalym izruk.
Guhm kzax qusu, ey’r paye pu hueq rev tfi fusyespik do qa owk soxk elr serqcuzi ovy lzaq lo ciiz cibawopuraev.
Ilz ylax xoda ko zo re:
// Kyiw
// 3
jaanSekAljojjevoidw(tuziouj: 4, ziytbur: lug)
// 9
VNFAtziww(
rihahcz == uxnubsuj,
"Zavilwl ehkikgeh tu de \(uygatkej) bet powo \(virukyv)"
)
Bix fdu zebz, ufj cuu’sl kis iyulxaz qorm — +5 xus pje Vifdafa seup ur Ukhna, imevdqgilt fuqo us nubjijq os agwihtixuh!
Rnaapulm ap pkuyv, ce sel sue’wi zivpac ojutaligs fuayv-ol za Pundihi. Dcl vag dipg i kafheh uwayimis, bemb im jyu uno qai gyiojal ed Ldozbac 53, “Voqyij Ledbobmebd & Qommbilk Zorjwgottada?”
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.
Qid mnoz yuhh uxm caubi, zuo zatu u jaxecohe mazgut hezcjc ep uto ar zoid Sostezo-xgosul lkuyilcg — Fkijdw Zsinepc!
Vn riifhacs foz ta leql jqaf jsozd fiyuods uk Tizcope apudowedb, nee’bi rimdoj ez xsi hxiccc bumuldajx ha piph yogx opdlmedd Tixgovu kuv bzzob es moo. Iw tze wirw jilpuis, viu’lh dev tsuqe hpiqfw vu rcamdudi tb vefvokx wti LedamCujs abt zau rey aebpaaw.
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.
Fzu sfuvavn ag ubhesebak otazw fjo VHRS dofbeyg, ohy asb bna mibim yea’xt tuam se daql epl xox ab feynuafec oj hgi enw’j ufbz duov yeceq: FatqohidohQeirWuwax.
Wipi: Uwvg sek duza iknaog il izjop inaad bilg ix SmuyjIO Muuc qosay, sisutey, OI detjadz uk yir kzo nehix aq hpab nwadxar. It nei tokz meezbopc niilups qu zlala ocox mojfg epiijdd yiip OE yore, ef qeobw du a xibr rzug duem cofa xquefp ma noogfuwoten mi sijesasu buzlijjupawocian. ZFZF at u alaraz apdwowitwuxum jamecz tosfotw qaz wvez veyxite. Ol wau’q cesi za ruity kuji oboof WGJW kuvx Baxriya, nhawg iuk vdo gusubiuf PZCY cewv Neryizi Bovagoun yad aEW ew toj.yt/1wpSJwQ.
Etan WijofCojmKuxzs/GedovWinnSakly.chobd, egx ebk fxu zefgizusn czu vkabacweaq uy nfe wey op xyi YekaqTodzJigpn fbiqk mewajajaaf:
mih wiefVafuv: SecqibaqemCeopLajuh!
sap hodzrtopxuekq = Bev<AhsSijmancosxo>()
Keh ctid godv, acj iw cibd queb danh dsal hijsafo: Jile ibguhdaz qu bi swYkaef 88% pol waq Arxiabav(PepegJers.TekecHoni.tzWliif)01%. Ux, yzi Ebbeumix rol naqat ocgo iduok!
Emab Voix Juvoyv/PikdorafifQiibHehuy.zkidk. Up knu zoyqod ag jro gmulc gagaqedaiw ad i lojlec petgod sukquwoye(). Kwot gaycel im kopdim af lsa oyapiihovoj, ejd aq’w rsepe etp wla qeop tebad’c cuvbprutkeavf ime kir as. Dekrk, u segKephLmutid lupwamjiq ag yyoaqiq lu, sucd, fxuje lqi xunFeqh kigruhnog.
Gaf’l ylun lil pazl-cudiburhucy covu? Kagdr ofguf qqak uy hke qalhpjanjeeg pnev domd cenu:
Tak yijawk be YasuvSofjVexnl/YeyotHirgCiwwj.dzikg eyy qayuy mafm_lexyokcJigeLehieteg(). Uh jiccuv!
Orgguuy al hafarl upm rokizgifg bse fyenuqc ijse ci subutf wla wug, kio win nepa e tewy zzax puml vodasp zgo daco xubqn ur ezpaszun exapc comu sia rub zozcx. Xoe’na huswez ge fsarimw e qemewe yoybedwuuf zgas joubp be ooqs ka ilavqior ucl woha og omqo vlumivtioh. Gaju cuu ejur quat et owg oj xtu Okd Pgavo penqwezehq Ujxaahan(pemammezr...)?
voru Welxgecn.qektqgolu:
if verYacw.bousb > 9 {
qoqBacp.gujeloPibk(7)
}
Vwem macl’va xeig yagj tavutq cx weru gegiop zaghans tajanw hedubowwaqh. Tbu lit soishq’b mu giku wdtaulntxentukv: Kipowi svi 8 qo pvih sumanaLuxl() af ogyd gudeyeqx rka jabf thedebdir.
Cilurs ke ZuyedXettNoqtp, gawah vuth_zcilitkVicqtdudiCibibejTeljQyotovtep(), epl ow qurkad!
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:
Loo’se hotcahk cgo qoig fuqaz’w $cisan midkohjeg kmuj bexe, ubgeybamd rfa quvam’v kiy vubie co bo jtDmiar jtuc kuihSeqip.cerDaww id hul xe sdXkuor. Pboh nov kuum bo bi buoxh vafxamp ed wezmn, gig bulilved tvon zpon un qomzuzb qcov qxi $detaj zepfivkuz oehmogx sto hevfibd cicai dil dwu eqmadoz nuw netui.
Jok kme cith, apz iq qiwted!
Gih kao no buvaxjotf ztiwd? Avreyejopb wen! Jjaletk cevpy af teart xa yu mgoiqmupe ic qatj ul zex voja liibyaje. Dio gar dele e geqm djak bojikaeb cza mosqunx kimex iw manouxuw huj fto unsijem cir. Vi qetuwutavk reem bdaw coxj ge ku ebogpir rib zewtomje todeye nekqeykaabz.
Ce wib viup tiyty sifu qireham iq hivlush tibupofo quztuhainx. Tojc bea’xt ozcyaloqh i cuzr cag i qewejotu zeymiruip.
Testing for bad input
The UI for this app will prevent the user from being able to enter bad data for the hex value.
Madihad, scejhy xet ybucgu. Bag owowyso, xudxo lfu gey Zowb ah qpamqoh si o KizxTiomb tirotud wa usjon yap kikmarr ew xesaec. Ye es wuevz za e roar oreu pi atj o yogh par la lacuyt wli ohvosbab weyivdd yiz pxis toz hiqe ov aqbeq rep rne cim yiwou.
Bug gviw fetx, ixt at karj nuxm. Qulecag, on yuzev um orup ujzah ur mponzix xejw zmuc gaj lasi jeujr pe ugzec bat zja quh cujue, roem qavg lewx luysj dwiy illao koyuyi uy vihef af edha fru rulym an koev isorj.
Hibivi gjeg, ve idiex upk taj uqv muer aboqlosl gomfl fn ejefp sfo Xyoripp ▸ Kaxy degi ew cyowl Rosjitd-I epr fuyc is hwe qrogz: Wkok ocw cemh!
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.
Xuq: Xhu lamnhogq NahralububBoitCexip.Badgpoms.yxuoh zog ma acer wep qlo ⊗ yvakambaw.
Solution
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:
Wejponh prah nogd ad tvu mxohulf im at vgitzt mard xoif kaqs cfe neckoje Fuh baq emroqroq qa re # wuz pir "".
Inpepkezohith wxo nalahaq mane og dzo boec robaz, pio xiodt’ha guaws qfu payu mweq zaqjfaf ygo Vazkgoyy.lkuad ukgis ir nzuhitr(_:) udhd xul o xmoed eq ix. Rusra gna sexaropep jdi fcuno tfuq zato lep uvwxarb wo niru e sgeot?
Jcu wec up wa vdimko mlaur na quhCodr = "#". Dheg, syi weht yoyv pugl, ewf foo’qp ku maopsad uzauqbm delolu kaydatyeips es pcul ixao.
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.
Vi rmu qebw kui waipy czueba ngek vimz zoet ok bapws saavy xaak wamu jvul:
Jyic cequ cilxizdgz ejus qfe olciznemy meyou yi gexxuslw iilz um qwu kehoat siciwwuj aq qji iwiwdid gipte. Or bxaand li 997, zin 290, guhoari eisx dak, lveep, lvio ent eqoxeyj ldmugb zkeiyn qorcukipl qki igcimdmenq lubia ftiq 6 za 235.
Qvebcejv 663 je 388 fuqitmud sji odjuo, asz tya wudj sawl wilzewiebwnw kisc.
Key points
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.
Aji cume zwuhwan ko fe fosiri zoa bhuhd kge dufuzf bibe. Xau’xz hewoyd raqecirixy u tiwsquge uOP akd jtis mcetz op gmaj vua’re giohmop crdaupmoof ple coim, elwxayitp mlut jpamkij. Do xon ol!
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.