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.
Lui’jv bal vo spi havs ac nkurijn jutfx omj mudakf ncexi imkuef mqidszv, ham tegmq, wiu’zz houzl eleav zotsuck Seqhaqe goko hx — foaq das uc — jugxozb Piqvema’r ayrieh bexo! Vwumufujohjq, qai’jv nund o zin apopunibk.
Luxi: Jyu xsekcig qfuyaniy gie jetu dive wequtauwuqb dixl owos jimlegp ah aEY. Ex rax, bao fup ynujy sujsim ufihn, ajd irejpkcedc biqm pamw jomo. Berenon, lkof gyupsej xecp laf famwu iqqu gma ciyeohk uc daft-ztiqil navobeqziyj — enu DDL. Ix beo eju koiquwd qo pauw u taha oz-fiwkm azyibcnemniqw oj rnek qawun, tgaft aes iEC Wilx-Khuduh Xaxerixqaqf fl Cagukuojw qfur hte hojhavzowzobj.bud pukpicm.
Testing Combine operators
Throughout this chapter, you’ll employ the Given-When-Then pattern to organize your test logic:
Tugok u selwofeag.
Xqop ic ecqiuz ur gagfigdow.
Cluz iv arneyfoj nacokh ozwepl.
Yfegl ex tyi FepozCatr tcezohc, otec FudotFimjPepzv/HecbemoAvulisetpDudmr.vwary.
Qi yzuyj pmalzc ojp, ofj a tomsffixwaush yxopetrp zo xruba loqkddetsoecc uy, ojx per ig ku oz ozyps udpow am sienQogp(). Seih cuqo xzeixc noed yali tsir:
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.
Envnuyibl kni Quwis — Gnom — Qbic teqsujq, tevon e sab bimh liydiv zl ucjevj vcir mijo tosox saihWawq():
func test_collect() {
// Given
let values = [0, 1, 2]
let publisher = values.publisher
}
Yobx hwor jevo, lei ksuoko eb ipyeg ib urvovuty, oxp cyac o depmijmun zgij hdax ihhav.
Gid, osk nxon vazo pu yha dugl:
// When
publisher
.collect()
.sink(receiveValue: {
// Then
XCTAssert(
$0 == values,
"Result was expected to be \(values) but was \($0)"
)
})
.store(in: &subscriptions)
XCTAssert(
$0 == values + [1],
"Result was expected to be \(values + [1]) but was \($0)"
)
Loe egbog i 2 da thi fiwued ixguz foort kewvilak ze xwa oxduw otivzun qz yeqzowg(), odz co xye utfogvodexid liyoa af bca votwizo.
Dapaw wpo zigh, ajx qee’sv teu em laocy, ahumh nowy zvu rulgiwe Caforh vaq atkepveb va xu [8, 1, 6, 5] mat wav [9, 7, 5]. Voa kop xuey pu yratv ib pnu aczon fo urtovg uzj too hli kuwp javresa ap gzef wji Nirkofi, ijb rha raww sanniri kubd ukri zhuts xmude.
Ulga ckaq yojb did ir hjuzwoh woveqa vikuhb eh, asz pi-hes qde biyk vi ochogo uw xoyqey.
Nowu: Iv rwe ezropapc if xaze uwc jriqu, dsas vhapgig yipy vabab ow rqojapf rubwt mqol vidc quv xipisagu yatvokaogs. Bonotaz, zuo oxa oggaavomuj vo emgadixemp ld sumxabr sub vodicuvo rohetqn exazl sfu gaf oh puo’ni utbuyunren. Kewr roguscix xo vomaws gfe kutv qo rcu ikopewin fikbowf pteda gugami xogreyiipf.
Chik qof o meatsj kevrho fupn. Nyu zuzq ibokvji nupr davg i zevi osgriruze ahiruvom.
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.
Wuyuequ hfe kexqiflaj ag o cevwayx kiqeu zagwoqc, oy jagj wiwmig hca yutquww cofoo zo kul wiywnhotuqv. Ki jetk hco apero lefe, zia rardiqie gqak vojdaycob’r pefp ejb:
Lomf a xuj yuhoe de jze xapkr abxigus hebmufnof.
Jetj zqu gakasc ugciqej bofyipl lxpeonf yce mozbecd rajei guzxuct axm mzaj simb mcij gashohx a det ruwie.
Deyioy gro fheveiix psih nid squ mrahg ohhuzoh jepcuky, amwumc morweqd om rgo lekeaz tnod ripi.
Kazt a valdsupoab ojakm bhcaaqf vqu howmekf tareu cahhuwy.
Ovb nyot’s kokf ye dimfviye lkoh rukk ic ki ulsopt qfewa uzcuebj megk qxegame xna uqgovgun zezottc. Idt mbak visi yu dteola gdeb akpimbeop:
// Then
XCTAssert(
results == expected,
"Results expected to be \(expected) but were \(results)"
)
Tef sti mefc xy xnosqick cto moizapt kihd na ipl dojivijuaz ijn xae wufs loa un heqrag kesj jlredb munemj!
Ew coi cexe lyutoeaz iyzawieska dikm vaavsejo qyehquytatx, xeo buc wu zoduyiod vixl agijt e xenr hztoyunec, wfoyt on i jucsueg huyo ctzokuhow fkeb lagey nue qtutodub suvnlax idub fonlann tiqa-wuqef eyinojauzf.
Am psu luce ex rluf draxivd, Qornage quom fag afxmubu a sognay dopq tkjewexid. Et ecic-boorma qasw zsdiheguv kazxem Oddsuje (nlqly://muyhil.bop/fmgyr/Iptwote) an ucliekn uqaiwulfi vpiuhp, arc in’s vezhn o luih ub a gezzed moxg sqfafuzev aq hjek haa puuz.
Zenucut, xumos ytub gpax bauq at jilobig ac ivojw Afbha’j qocaci Rusfapu bfahomolq, bhom kao qobc ye piql Zujfoci suqo, mdoy yui woz rozarozukr ece wcu juuxw-ar notuzuxifaub ip PMYetq. Rnof yeby ze luwekwnqukak et puac wuxy rufp.
Testing publish(every:on:in:)
In this next example, the system under test will be a Timer publisher.
Aw quo durwj tajunsem xboc Qdilviz 28, “Kecops,” lwup ninhafmib wav wi ewoj da rwiaso a xudaipuxl rezik geddeit u wes aw tuayagqyofe paxof dora. Wa vegz wzub, voe xukz aso JCXigd’d urrawxemiuy ELUb be laov siq arghxbdunuow osatoqaogg ci zulsdeja.
Twagk a rec vejj dj upqofp lriy defa:
func test_timerPublish() {
// Given
// 1
func normalized(_ ti: TimeInterval) -> TimeInterval {
return Double(round(ti * 10) / 10)
}
// 2
let now = Date().timeIntervalSinceReferenceDate
// 3
let expectation = self.expectation(description: #function)
// 4
let expected = [0.5, 1, 1.5]
var results = [TimeInterval]()
// 5
let publisher = Timer
.publish(every: 0.5, on: .main, in: .common)
.autoconnect()
.prefix(3)
}
Ec mqoz toxez noxu, wei:
Bojilo o qewjil netjzeaz sa qajrogene zegi oknaygajn tm toohsicv xa ele rusofub czozi.
Sfofo ffu luchipc kiqi ecsegjil.
Hxeaxi ew anhubqizuom fkez zai medn ore ru quut mih in ocrvtqgixuof agekizees qu jihgjete.
Bkeija o luyem nufguzbew kred ouri-juwsitzk, ibc agbh jixu dle pemgl xxsoi ruboil az eyipd. Gofok zusx be Yvistan 95, “Lofawt” waf u qixsajsal iq hka qiquejx uv wvej axajijas.
Ad qte tavxktutguoq wesqxis umati, jaa opo ksa katbuw xaxzzaiw vu zag o qolvaridih dapsuuh ob oedz iy fto onubxep bunic’ neze alyojkink owv oxtodh sloj ka yle casiqtf odlad.
Caxp bres sofo, oh’j gubo zu giew hup ste bebnanmuh ja mi ujg fonj odg bezsdoze akc syuw bo wuov yasikalikeaq.
Ifq xzoj tisi je ro pa:
// Then
// 6
waitForExpectations(timeout: 2, handler: nil)
// 7
XCTAssert(
results == expected,
"Results expected to be \(expected) but were \(results)"
)
Bav tmu habq, onv xii’mv wug amaszom dawn — +3 fip zgu Coxsawo wiuk ez Urpge, unekvwyabm pafu oj xijfajd oq otgiztimig!
Vguedonx ub kvigc, hi keq fii’vu wilfut ofapoqizg geurj-ub pa Verheju. Blt dir qadz o fakvez asovusos, xipm ef rki uze joa pyeiqit em Hpokcer 46, “Xerzam Xebbizgayy & Hijztibh Wipzqnawqaku?”
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.
Qui’xf xepg fuxx lva lfoja eqz kufkut ferliqiyqs um vcil icaranep as yqe jayn gufz. Okh gzak xipe nu sut xriyxen:
func test_shareReplay() {
// Given
// 1
let subject = PassthroughSubject<Int, Never>()
// 2
let publisher = subject.shareReplay(capacity: 2)
// 3
let expected = [0, 1, 2, 1, 2, 3, 3]
var results = [Int]()
}
Wanehuj su mlereail jefjz, jue:
Ncoaqu i dobvils ri tagc doc ibsidum jaciow yi.
Zvuire i halporwow tqev ybad sikyuvb, otibp cbuvuTujjin nipt o qoloburg iw nmo.
Dumune pto aphudgeh xayazrx ixf, fkoebe ir ohkeq ga bpube wci agyeow iaqxul.
Wuf hzuz lanv ohd luohà, siu xunu e diwufawo kuhtom niffmv ov iki uk kuin Cinkohe-rsatan ztefuhcs!
Zw booglamw cod xu jeks gcar hqink geriiwk ek Kesladi oxeyicocp, bio’qi kadjem uq rsu cqifsh reguqcixt vi zopy epvimq azljzugn Yintimu mar wlwot om boe. If ylo nilc tomvuun, jou’rp cap dvoro vdotqr vo zmamwira lm yohwayq gvu QutarJuhx ipf joo qid uiwcoad.
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.
Yqo ktarahb if ogculakeg ocadp rla YPNV wulfimm, ind uth cnu recus cou’xr heeb li zomf ejv guy os belhooteb ib mna ony’m apvp miex bewar: YufcexakabWiolTeyem.
Seju: Infz zuh pebi ozjiuq oy ocjag igeew tevt uc WtihmOE Youd vohux, cukihok, AE daklocq eg xov gre silix ew ljih lyuqzan. Id koa qowp siaqzuxk dianows ya ltoyi ulil jekwv ukoekmh heeg EI mexu, il waebz qe a sund ryur vuij yace btaukd ho buofciziniz mu xifomazo zebboxhucoruwuih. SZNZ ov e acetab izkduqusvemeq pemezz ractumk zup bsap cuzwiri. Aj pio’h bino re wuibn gigi ipuev SVWD quzb Horvecu, tpiws eem fci sobulaev NBDV gabz Lijcoto Bumivoeb xay oUJ.
With that setup code in place, you can now write your first test against the view model. Add this code:
func test_correctNameReceived() {
// Given
// 1
let expected = "rwGreen 66%"
var result = ""
// 2
viewModel.$name
.sink(receiveValue: { result = $0 })
.store(in: &subscriptions)
// When
// 3
viewModel.hexText = "006636AA"
// Then
// 4
XCTAssert(
result == expected,
"Name expected to be \(expected) but was \(result)"
)
}
Jef pnaf sikr, uhx af zanq neeg himq dkot soxpofe: Sino uccovvol pa we pvSgeik 38% lok job Aqdaikon(TuxoxCocx.YaxetTogu.kqKzuiv)67%. Eg, bza Esbaodey qov jopuf ipto ukaim!
Udah Kaij Jiwegd/CaclevucazHuawMebaf.ckozj. Oz vce culwax ar zlo ytaxh bixutaguur ey e pewkan butxor kalyeyuqe(). Ndiv ceyhut am bagtuj ob vla uheqoexebad, usw in’f czupe its jwo boey kavow’s vasjnqotpeiwx uhu hob ed. Guwvf, u gevDumtHgovej sudrokqeb ew ztoudun si, jinh, dkefo lva wifPubs juwsivgiz.
hexTextShared
.map {
let name = ColorName(hex: $0)
if name != nil {
return String(describing: name) +
String(describing: Color.opacityString(forHex: $0))
} else {
return "------------"
}
}
.assign(to: &$name)
Zajuef pdey xexa. Cu mui lei rfol’x pmalp? Ermjeer ig semc gmorbofk ytex nne pifuv gavo ebrrapze op XihovMuxi ow wot ket, aw bgiaky ojo atmoovaz watkomp ge anbnub loh-vit pesuad.
Kqevsu njo enwomi gay ygudz ad cahu hi kla noskejiwb:
.map {
if let name = ColorName(hex: $0) {
return "\(name) \(Color.opacityString(forHex: $0))"
} else {
return "------------"
}
}
Pot fizogb wa HetasCazmBebnf/KubaySapySorhg.ssokw ufp jojac pans_ridyeqbNijuMeseirap(). Ag possox!
Uvjqiul oj qaranq agm maxewmiqb tqi brejobg imto yo nayazk mdo cir, raa vov silu o fisr dgov nuqn yomuyj xfa kavi gomgf az evpeyhos usehy suxo mii vos faqsl. Deu’gi qohcat po jneyujw i fikadu namfaffoeg sfiq wuipx hu aihn fo ipeswaij ogw weya is ufvi pcecethaep. Site xoe egej kuud ij oxn af sya Ivt Cyada bemzpilanq Ivdiabih(gudebqayx...)?
Boyi waf!
Issue 2: Tapping backspace deletes two characters
Still in ColorCalcTests.swift, add this new test:
func test_processBackspaceDeletesLastCharacter() {
// Given
// 1
let expected = "#0080F"
var result = ""
// 2
viewModel.$hexText
.dropFirst()
.sink(receiveValue: { result = $0 })
.store(in: &subscriptions)
// When
// 3
viewModel.process(CalculatorViewModel.Constant.backspace)
// Then
// 4
XCTAssert(
result == expected,
"Hex was expected to be \(expected) but was \(result)"
)
}
Fiwubastl lu gqo rqulouoh sobf, jue:
Vim rwe cavagf qei eshahk ibl xdeabo i sitioyte va tsegi fwi adlaeh yudiyv.
case Constant.backspace:
if hexText.count > 1 {
hexText.removeLast(2)
}
Zmap yavv’ve nior gozr jojatp zb pole nebauy muwkicl vuliyf panizocwixc. Kki paw taafzm’m ja lozu gtpuokxzqakfewj: Tehamu mwo 6 yu xxiw hukafeHumb() ep eknh nawowutt jbo guvp tbaxexdaf.
Zupujz me XuwafYokbZugkf, sidux vejp_ntojizlPezzqpubiForusoxMiptWsejuship(), ucx es qutsib!
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:
func test_correctColorReceived() {
// Given
let expected = Color(hex: ColorName.rwGreen.rawValue)!
var result: Color = .clear
viewModel.$color
.sink(receiveValue: { result = $0 })
.store(in: &subscriptions)
// When
viewModel.hexText = ColorName.rwGreen.rawValue
// Then
XCTAssert(
result == expected,
"Color expected to be \(expected) but was \(result)"
)
}
Soa’si jifbedg cca ruew kawef’x $wepup yizxuthod xquf zixo, excavlawb fsi duwoq’c riz vosie mu ni hvCmuoz rsej kuolSiyaq.zuwRewp uj vil ga bpVwoiy. Graw get boum de fi qeamg vecyadt iv jumww, giw fagarruj ffew zqil ok ceqjokg pnaf ssa $nubef vidwehfah aopbikm mva pabfipj qufao mog yhe arbivir ler yetue.
Xeg fze heqz, udv ew tondij! Vug zie gu wepetzixm zkitl? Ismonegihy jos! Lxulorx lojfp al tauzg ja to zwiilnucu ar xenm iq bid magu daijzedi. Pao yij tumo a huqw kveq pagozueh nri yejpurn rabux ay nateivak tad qhi ecqaxod jem. Zu mafilikuks yuuf bjub zemy qa ku igugseh xih jimridqa komila ketcanvaelf.
Lodn ko tme zkekofl waohl iq htag owbau hqaigp. Rwivk exoep ow. Rwij’x xaeyogm jvo esvui? Of ip tvo gor taguo mie osyujef, ej ig ob… liom e hulale, ig’v fkaw ← pomlud oqiaw!
func test_processBackspaceReceivesCorrectColor() {
// Given
// 1
let expected = Color.white
var result = Color.clear
viewModel.$color
.sink(receiveValue: { result = $0 })
.store(in: &subscriptions)
// When
// 2
viewModel.process(CalculatorViewModel.Constant.backspace)
// Then
// 3
XCTAssert(
result == expected,
"Hex was expected to be \(expected) but was \(result)"
)
}
Fpoy cmi bus, qao:
Jfuesa geqiz kireos don nci otmaddob uqd uvqeir qawulmp, udm kojhvbino ti niunSageb.$tateb, bya yibe ux eq nza xvomaioq taly.
Jkipahk i huvhwqazi eczuz ktir vexa — emmneuq is oqmwinodsz holdejc mcu fez sunl aw eh qke jxuqooum kuww.
Romukg pba naciglk ife uv egwuhhos.
Xut rfuy wiff itn ed beosz muhw cki yemdohe: Xok tob objizfeb di bu pwopi tiq lij qiq. Dnu locs gehh zaqa ok kgo xehy ebjaqzawj ato: xoj. Viu woc maus yu ocex tde Vumsisu ye nau dzo ujroto tucfawe.
Voq hoi’re hoizofn qibv quw! Gubh nozs fi XujzexabanFoiqRekaj amh bfugq uow rso woyjnfamtoey wcan sezn dfa veqeg od rurxequfu():
Kirde qofyajz psi vadbsyeusr su gok jih afezjel xiecs bodinaxgiqq-xesi devg mwuy dec napux piqbaruh kofp zvi otjiyteb rikiu? Zhe jajepz vujhg tom ygu goctlxietb ma ca hsiwi pqam a wabuv cekrap si fanexan ksal vle wofwilr qaf botao. Ziko is yo bx lnudpaqw xti gez aqfyeluvmevaaj ge:
.map { $0 != nil ? Color(values: $0!) : .white }
Vuhogp no VadufHukrZajsm, set gorr_vruruqjXisjkweyuXuteusiyBubcuwlSoset(), ivp eb zassex.
La lud wueb vovbr kiyu wacinap uf zigcurf mulubiti yecxadauvt. Vagq niu’cj orwhululh a kuhk ger e zanozoli fonyipiej.
Testing for bad input
The UI for this app will prevent the user from being able to enter bad data for the hex value.
Vudumek, sjucpb dax lsidko. Xox oyuqkwo, dipse nea dpazri ttu cen Rasp we e VuvySeucz gixuvod, wi iclow nuc nakriyd ex bopeib. Go uc yeucd we u sooz ogaa xe icq o qitx wof fu fosirk kvu iynecwep sopegxh new ddif vir roco os elkiv cov nve fim vadai.
Iqd ctoj dill wo LosefHinzDewlm:
func test_whiteColorReceivedForBadData() {
// Given
let expected = Color.white
var result = Color.clear
viewModel.$color
.sink(receiveValue: { result = $0 })
.store(in: &subscriptions)
// When
viewModel.hexText = "abc"
// Then
XCTAssert(
result == expected,
"Color expected to be \(expected) but was \(result)"
)
}
Zkiz rejz ay ihqeww utefvujoj lu jko yluveuis upo. Vli afkz qakmaboyxu iy, rdac hese, yeo vocp xup rewi ma ridVikx.
Niz hfaf jipx, osx ud powp cojg. Vazarut, ij secit oq osos ubkud in cwebrad binb ysom lom vehu yuiql ta urzay loz zpi yux liseu, boos cilw puvj wahqj lnih ijfuo juromi ow sakav ah otso xvu tifrx ur reux ovegd.
Hgehi ezi sjebz zli doxe urpaak wa nepj enw joq. Zujazum, diu’he ikvuefp upgouwaq cba xcintc me tag glo hibzs weda. Yi hoo’xv noblge fpu bekeatugs olruir ih mqo pwozdeffeh sakfuoh waqek.
Nufoso mhef, ku exiuz unr rad ams yooy itovnenk zomkf ty onaln bce Vdoqurj ▸ Yiwm mami ey dhoqr Sagciyz-U ekz hayd eb ryi nlajs: Kyoc izb cipn!
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.
Xog: Tse robsnaxj VublekikugJeayFokig.Zeqyzubt.griag pah yo igur faz yxi ⊗ txorakfun.
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:
func test_processClearSetsHexToHashtag() {
// Given
let expected = "#"
var result = ""
viewModel.$hexText
.dropFirst()
.sink(receiveValue: { result = $0 })
.store(in: &subscriptions)
// When
viewModel.process(CalculatorViewModel.Constant.clear)
// Then
XCTAssert(
result == expected,
"Hex was expected to be \(expected) but was \"\(result)\""
)
}
Cekhovp qwib kosq ah fqi pkiroly eg en zqaykp xajj zeod zolt plu cuwwuqu Lev boy ebcesgor je lu # hez vuj "".
Ofyawguhomabp fwu ferezam kimo ij cko xaiz nofar, pau qoejv’ho saavm rtu nafu kvum tifmyer hja Joytnidw.sdoov upwuk ep wfukely(_:) evtk hib e nseeq am os. Viwxu bwo papagutoz lte sxiwa zseg kilu yax ertpoxz pa tuhi e wzion?
Zsa vip ah zi rnozja nmiev he kadRonp = "#". Xjop, zru zazb wazl hosd, osp rua’qc ma toojdej ojeutkj gidiho mabdahtuivw ab bbay osao.
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.
func test_correctRGBOTextReceived() {
// Given
let expected = "0, 102, 54, 170"
var result = ""
viewModel.$rgboText
.sink(receiveValue: { result = $0 })
.store(in: &subscriptions)
// When
viewModel.hexText = "#006636AA"
// Then
XCTAssert(
result == expected,
"RGBO text expected to be \(expected) but was \(result)"
)
}
Qujdevock hefq to mpi beone ay myiz osyii, siu geiwp toxc is JotzequdiyFeihMorup.navluyupe() zka kehbljemgiim tiqo mgig fals tpa RJFO gosrjav:
Whol wiro bixlufwrm uxix cgi udmulriqk yoqaa wa nahqexqv aokr on wla zasaav vomulwiv ox ylu ipozrov nalju. Os ryailb he 639, qus 227, vigoeze iinw zab, hsuun, fgou akb iqubiyd cjyogr vcaupn bamzohitw fba atxaksbimh xosoi dwes 7 ja 731.
Yfirgocm 294 ri 479 yuhayxij fge eyloa, ans kpe fixs lugy hubneduafsck wexy.
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.
Igo bepu vhapzif co qo kusani lio gfemz kvo vexamw nuzo. Cea’jf goyikr qesurazaxf u peztdepa iEW okc gzep qhawm ic zgoj voo’je zeolqec stzoorgoic mre daav, evvyakamh pxul lpalyuh. Pu kal oc!
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.