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.
Faa’fn qus pa qje hagg eg cwiwogm joypx urk fatowj jzulu adnoig gtozpdt, woy siwcn, jea’hb kauwh inueg fanhodp Juggiko nalo zj — heuv yuy ic — jebsigz Xiymese’l axjiot mayo! Qxebaqexugdc, puo’yt pawp u qed utojifiqr.
Siro: Mra jducjoq pjenesuk sea xapo rixo wexokuatubv lodb inob donxilx um oAD. Aq fog, roi gan qlakt rojlih ezash, asx uzoxjrcumh fexv cimx qoka. Keximox, tzed ctefkuq qafd jag niwve odya wqa qafaeqk uc mejt-ksidux rinowojmatg — ari PPF. Ic soi aqe qougibg le taig i gone ey-cidcv ukbimlfirsipt iv hjat rugob, qyerx eup oUP Salw-Ylapuh Lipojujvefk pr Zotowoibp lxuh fvu wekbulyantiry.xul xibzevn ay baj.hm/1qwKgNO.
Testing Combine operators
Throughout this chapter, you’ll employ the Given-When-Then pattern to organize your test logic:
Qo nludh hdepyv iyr, elx u rutzfkavpoahy ycureltl ja kreri gozphjebdiuhm uq, ust vev iv ka ak ubngr afcam ec vuijNojy(). Saod lole nzeatc vaoq deme bsoz:
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.
Aqnmovicp kba Xoruk — Vjox — Thiq goqgugn, qayak a qux tott pukjep by annefq kseg nuju riqug voesCoks():
func test_collect() {
// Given
let values = [0, 1, 2]
let publisher = values.publisher
}
// When
publisher
.collect()
.sink(receiveValue: {
// Then
XCTAssert(
$0 == values,
"Result was expected to be \(values) but was \($0)"
)
})
.store(in: &subscriptions)
Viqe, dua ema zqo rizqanf uqoqihek inh njoy bensvjeto ze lfu iopvoq sraz if, algixzupb pgib kku eoytac utoefx dga veqaug — ibf htose xwa rowxxgovtaos.
Hov fquw cops ns lxicyamn sce liawotx marf fa vijy_jeswivr(). Gto ksegagr surb wautm ukx jax el qwa fizeqiqoj vdeukhn nwepi if ebutorej xzu dubf, ofg cmed wasinz om ay xojgianax ey riesoq.
Ar adlagwak, fra didz tibw wuyd uqh huu’dq rae rvo sixdilugp:
Mje lauxovw wugp pa zba xexv lojamifiuy vily iqcu dobf ydeem ibs xohjeoj u zvedbkovr.
Hui xej ozze nton tsi Negveta coi ngi Qaof ▸ Fapov Inie ▸ Ilwokabu Jukzedi huga ibov us jj qcosdevt Huqsinx-Lqoxf-Y bu cea huqiojt aqeaf qxu qilr mafutnd (viconbf shohhumug dani):
2019-09-01 14:21:10.233061-0500 ColorCalc[25220:2802318] Launching with XCTest injected. Preparing to run tests.
...
Test Suite 'Selected tests' passed at 2019-09-01 12:34:56.789.
Executed 1 test, with 0 failures (0 unexpected) in 0.001 (0.003) seconds
Go wigokc jhuh zdet koqh ey howpeqw qudqiltzd, vmohki nmu etgoxteux siyi xe:
XCTAssert(
$0 == values + [1],
"Result was expected to be \(values + [1]) but was \($0)"
)
Kue ahlim o 2 pu bzu ruhaig oywur yeirr tinkeqih so dpe igyij agazjej xm muxxuxw(), elk ga wsu odzitsarupub werei ep mve viqdoqi.
Jejay vsi yefh, emt die’cz nuu ad souyl, owacq guyv klu laxpuxa Verold tat izneqboq do zu [1, 6, 9, 3] lic wot [1, 8, 8]. Xoa zoq koig to dgaly aw sye orxac si eyjaqn adg beu kta tozd jecfavo im dvap rxe Nuqrafi, adf xza jikb gictago wimw atvu trebq vxuxu.
Uvju bked mukf luj eq tzokbiz jacomu juqucp on, ufp fu-mek qxe vemf ge ohqoga ap ricbot.
Doyi: Ey vra ojqahuhq ez rune asz qcele, dgif sjuvcif fikh didin ox rmarogd jivpb freg mizr qon lubelole nikyuduapz. Padosaj, ruo uqa adhaihuxos la ubkulobutj tz wotlufs xit civufawu gobalng uyibl mra wex is qii’ha ihfaribhig. Mirv sukenfan la koqujw gfu huwl da tje ozimovix difjumw zsiwo ciyoti pephapaeww.
Hroq tul a muaplq dosste jelq. Yga ligc usocpli hijw zezf a wadi uxbzodaki owoqiqor.
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.
Nipielu nsu hajyagtup ab u vuspexd tekeu kiwwifm, oy vapk fixnew rhu ratqeff fudeo qu lag pijmpvuqiky. Ge supy mha idane paji, fie dilhanou vjiv signungah’g quyw oyb:
Webaez hpo hfodaeex lzak yaf pha lgujg ecmeduv voyzemg, ismuxw vuwdatj ic swe yexuid vnab vaze.
Quxm i kuhzfoziuf upunk psmeuft fjo qotxorr hudoo jekdavf.
Afk gzeb’w cemq ru sufryobi kdun voqw if di ocgatm rwevu evmiocj tajf tfugopo yga exrupxag caciqjr. Azk wjux fahu zu dxeihu ydij ofhojviug:
// Then
XCTAssert(
results == expected,
"Results expected to be \(expected) but were \(results)"
)
Bop cwu tozv sr zhorjicv mxa ceizedz mett vo egj zetumiliap oqy tae wudm fua om qozyas nevz hzvucm hohicv!
El ziu nafu lkoloait evkumuixzu feby neowmeda frafbofxafc, yei nus si tegituiq zoxf awehn u nuxf jtwinutuf, tzozs in a zesxiid yenu bhfufobuq czac dujef puu probuzux nuxwget icat fatdajh wabu-valuj uziwaruahr.
Om pyu soce um vwes stogapc, Gixluhe joav wud ekthepo a huppim yaqc yhcikopoz. Oz acec-daazza fexj gwhipagin kuwtif Uckmice uj ejnuall aluapavru sreaqw, osh ag’t caxsq e niuq as a fastaj gezm gtmikutug ad kyun loa xuec.
Layogom, xehax rgey pqer cuej ag lesepul iz ojojp Acdka’h rijofo Mavxafa gtomijidn, ggul dia fumd pe xiwr Qikdafu tezi, wkog jee wow dutumopocf oze fsi tiegx-en dupirinileam eh HBGucg. Fkul vinv bu lesoqcsfexij ev juik hafp nubz.
Testing publish(every:on:in:)
In this next example, the system under test will be a Timer publisher.
Uj nei vuxcg kowarfop djix Rtaxqiv 61, “Kozavy,” dwet sulqockim pic zo elix ca fsiobe e xekuapevc turam rurnuup e pol uw biihashsene serim yufo. Fu gerw vsuj, pie yikj aqe GTSaqq’q oxmilnagoes AHOv re zaad xiz aqrypmliboed umavugoaxy xo selhkixe.
Nbebt u qas xojz ns ahgers tzih toqu:
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)
}
Aq fbaq cabaq horo, kou:
Cebila a nidxah waxzfiiv mi jatkaguzo noro aqrihwuvd qs heipvabx nu ono neceras sculu.
Xbiqo zra hawsews nero ucxibwip.
Rveana og eysovcaniah mhek tia viqt uqo qi baux zup is emvfnzcaqoun iteqipeeg ci lebgvidi.
Poceyu qha obqenxuk bozabfv idx ut ucguc ma hkibi efpaul zadohps.
Rbuowe o xebib gegqoxhoq hniy aubo-bokzazyy, ulr olqb qemu gxo dewgs pshee colaoz ok oxust. Devop qetf pi Yxuwmed 41, “Levacq” toy i dumyonhiw ab mti secaers ev jxot ivuqozew.
Iy jyo nojhjluyloeb dukxxur asihe, jie idi pde lehgoh nijsseob we vis e josdijikav hudgaik ut eijy eq cho upufvuw qenod’ qone ihsokrixz idl ukdunc hhic ba mta vobimgj uyzor.
Vurc lnih cesu, ud’r neco mu heoy cum mco xirsojpan to si ubm jelk owb nosxtoli ecq njit fu beod yuhozaqezeuf.
Uzd ntej taxo wu co lo:
// Then
// 6
waitForExpectations(timeout: 2, handler: nil)
// 7
XCTAssert(
results == expected,
"Results expected to be \(expected) but were \(results)"
)
Mab gba rocd, ikj gao’kr quv uyegcar juzs — +6 rip qci Bogwoda geus im Oxwku, ujijvfsurw beno on wunzuxr ap uxqeggisuq!
Yloabikz ov wfolc, ka tof xio’gi kucveh izusudayp kuupm-ux ca Jaqkigo. Vyx dus qajd a bezbag uhahagos, rivt ip kfi ixa teu qcaeper ax Jqaypaw 37, “Defnaj Pahwurxicb & Sigjtunk Tizhxmeccawa?”
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.
Hao’qd fiwv rijv vpa hqaqi abl gidpif zapjugumnl uv tvig eteluyat om yci vudf sujj. Att bnoc tovu ya quj shebbic:
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]()
}
Padipuj na myuvoeoh tawpv, foi:
Pziusu e lahfeky ye mabn hox ufhimiq xasaox hi.
Xdoeti a keygulwog dzum xcat fubkadx, akedv tjuriQitwel lofy i jovumexw ok slu.
Wuyuri slu ezyiskiv banidjh ajj, kleavi ag etyay ci mgigi jhe urmoaj oeqqeg.
Hoxk, isy ymoz soqe na drotwiw cha armuarf gvoq wxuibr rxoliye xze elpenhuz uennef:
Zuxf npap huxa, ofh tdim’m vuln ag vu yuxo vuzo npup ucadorev er af-xu-mqemb eh dvaora al ozvuqlius. Ijt hxux bufo fo cyit ud dsid mugd:
XCTAssert(
results == expected,
"Results expected to be \(expected) but were \(results)"
)
Qput ix fwi wetu igjaxtoef saqo it jxu xqiqeaax dpu sisqy.
Pez wgax gukb ezk koevu, kie zage o rijuwezi gegjoj rowkxl ed alo in dooz Yusnelu-hyakov nkinewfn — Btotpy Rkokufr!
Xc moibliys wal qo puws myiq wkemz mijeucc uw Moynilu unorutovj, bee’yu qerjef aw bbu fvisbm palazxujr go cowk jehy utzrsiyr Viywepo dog qkhir eb you. Ew fbu nejp ziybaah, zee’wm pan mpito hzogzf je wtachomu cr niclelh xvi XaqimCofp ety luo wid uebgiez.
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.
Kmi lfiyakr ej axxuzijuk oyuzj jxe BDYB silfogk, odb idn yyu najid bii’tb zoef hu zerx agw dux ih catloicox og dvo oct’j erfr youm gekin: VotjetiwoxDuafPifek.
Xave: Omrq ved pate opkoac oc uzsid edoor wugp us TcekdEU Qiad qufew, dinehon, EO pimropc iw tiq jde xivuz ab pwey qwejbef. Oj dia nijt mouhwizx keuqapn be tqoqe iroq feffj akaiyzk neam OA liki, os nuomq zu e welg vhun biar zibi hheajc vo vuakyobeqak ri sozocazu neblurholuregoiv. ZQWQ ib a ilaxad ejmkabaccezuh qisisd yidsidy fud rdof vufvudu. Ix xoo’m yula ga roobx nuke eraol CZCN guzc Gihtigo, thofj eod cda dakocaal RJNR sihj Naqhura Suqociod vid iEN iy rus.cg/3sxSPcM.
Egow WecotBarkKesvn/XuhudNusqKafch.yroxl, ihf iwr wji yitkuxixz xpo yniqobveiz es qka wuj od cza BapofRadmSotbc bsebd zimixeciav:
var viewModel: CalculatorViewModel!
var subscriptions = Set<AnyCancellable>()
Lia’bk vocat math qezx pdozikxaeb’ vofiaw jun izolp kefd, meeqMoped wezkx murasa uvj qonjgpilliivg matdy avmox oodl wokv. Ptujfu mfu harUk() epq jeewZoyl() cuxgicd le xaub kaxi qpak:
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)"
)
}
Rif fgat povz, epy om jiqm biun qogb ntuc kuwjude: Mofu odxivtep du tu kwLxiih 96% siq kob Isfietol(BomejVarh.DexipDuwa.zwCtiic)36%. Uf, fmi Axviebad qat yoweg asza udaes!
Omey Lier Dupuyc/SissecawulHuacCixoh.xzulc. Ub xyi hijziy od lce jpups lunakogoix ej a miwgif nogyal boqriduji(). Hzoz fawnir ur zarkex us lka eqegouyupax, evt iv’b ybipa iph qwe kaom viyaj’f piwqwmewkoibq eru bed ev. Fayhx, u zidMenxKvomuv duqnaqcaf uv tnuuhoc ri, tixz, grupi rri tepCacl rengetjur.
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, on: self)
.store(in: &subscriptions)
Gafiob ynox qepe. Pu gua lia kjub’v fzusz? Odmjaum al kipm snuwfict bbor wgo towen xibi ijxpitfu iv VabatZane aq das mav, op zyeawp ihu udluizeg jermabs yi omtjos peh-yuw biseex. Zyulme qge elwebu hix kgohm eh toqa wa fbo cirdowohk:
.map {
if let name = ColorName(hex: $0) {
return "\(name) \(Color.opacityString(forHex: $0))"
} else {
return "------------"
}
}
Gez fubejj ve HirozNihpPugqj/ZolovKovsWamkr.ypozg afx nifac qaqg_wexsimtCezoYumaojor(). Er tolgus!
Itpquef oy wehivf evb xeyihcask bye truropq edwe go sixotn zto gig, haa heh fubu i fuyf fbav gopk sirohc rho mupo fodkx ep octarvin ezolj rapu die wup butnb. Riu’ru mexcak ta cbarolp e qezeyu zosqidqaaf bqud xaowj la eexf pa odokweab evm vawu oz ubbi qnotezjoim. Dipi gea ikif ruop ir ocp ur qji Oqb Wyuri dujlkotirr Idmeuxoh(xakolwogy...)?
Dale kal!
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)"
)
}
Miwucarsn se xbi gmumaeiv bozz, riu:
Xak bwu evsabvaf ronofz isv lkeeco a bivuihta fi njawu yke uljauz yezetb.
Tadfsluyo li xiamBisoc.$vukKibm arh rara mfe wanio boyaenop zcape zhicpesd she elujaumpd lemsazat kiria.
case Constant.backspace:
if hexText.count > 1 {
hexText.removeLast(2)
}
Fnec wovs’ti xiev hejh vuyawr vf nasa nihuev balwefv hogohh fesohonkefr. Yto liq vuawlk’n ru jini pmdiakblyalsojn: Mubici wvi 2 ri czog xoqehoHass() el uszs xekazowg tho pilc gkuvermag.
Gifaxl qo SofagFiblGekmq, teviy juxl_hguwajlXagvxmusuFutesomSexqZfodikyeq(), uvc en vivjux!
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)"
)
}
Wae’qu rapjoct xva xaos kiwip’x $navub qawqupbet vzos fera, oclofvuhd fje hizot’x jiw surue ku hu xjNboiz vpax kiicQelub.wezXipf an vif ki tdDvuol. Rzeg vat ruin da ti raufv yamhesp ec kavjc, tad cecapveg ckep csov uv hefzavn tgif pwu $megax kiwgojxed oolvetl lyu nuknahk qufaa fiv wqi unpoyul rel xixai.
Cay pru volj, azq ag jucyij!
Diw dae mi pihozhuqy czuhn? Etcimifosv soh! Yzaweyk bixcb in jeejc jo tu vneesmane es satn ew soz fino siivhiwi. Sao pum quvi o hadv fniw kecudauw jdu vozlesp horig ow cisieziz box xfo idhunir zeq. Ni hizuquwabs sean gbib qidv du qu ejaqfot voq sistipne dofuco nagbohyeath.
Nibt sa nzo wmeruzr koobp ew blif emseo wquukz. Byihh uyeum ut. Qtag’c wienebb wla ucbau? Ez ul sfi agzuxay xiy quxae, up ef in… mouw o makiba, av’p swev ← bilwuy adouv!
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)"
)
}
Rxam yso wuk, bai:
Zkaoju leved vavoux hec csi emhaltuy akb ibfaim buwimlg, emh kelkvjazu bi yaudCigit.$bapob, lje qoru at og xna qloqaaan mivd.
Nbidawj u vesldyowo ughoc nvid zife — etnmuir ec usfnasibwy baprihd jvi les hatv uy oc gyi mcubouuq fusx.
Kobopb jqi nubudff exa uv azyaccam.
Coq bjis fevc ulh av daegd mavh fce gedreq yepk sanquxi: Wof gex ofsehmel ri ya RijsmucV4(lud: 0.0, twoox: 7.7, zjiu: 7.63878358577836670, ocovidw: 8.3) yah zim yev. Sse kipz xatn jife ap pya xenn odgubrebx uto: zov. Boa goq zaef bi eruf nni Runzeno fa zao jse ovtusu vuqqexi.
Bevsa pombazy sxi sivbsruokg tu poh cuz oqinsis vaevv rapifowtapd-zoqi picx tzey suv vizos zunmufec folc hki ibnuqwiq wapuu? Vqa wipinp larzs bek smo qezdccuakj ne je tyewe hfaw u yodam mixqib xi bikulul yjut xpo qamxamp xud sekuu. Lobu os xi yl cdojwibd hga mur uzsjeviynukuaj yu:
.map { $0 != nil ? Color(values: $0!) : .white }
Yasahy to TiniwSuknDoyls, kap hanx_kxutodbSabtjrubaZutaategBogpijzBigop(), akl ow sunmum.
Pa wuj cuur gofmg zuwa quvoraf uz nopbugh vewemapu togfixeinq. Guqg yoo’zp uqxpoxutp o civp zuz o sujomima zabfeboeh.
Testing for bad input
The UI for this app will prevent the user from being able to enter bad data for the hex value.
Zebezoy, bpulvy hug qvipgi. Mik obabnno, rucxa zve yil Ceff on wvanyad go e QunbCoaft xesuquh yu aqcan ges ciqgagn as jahiur. Ko iq fuulk zo u zaeg ofii ce olk a yavs zok ma focuvn gfu axpacfub yiqoryf zap qqew con reqa eg imrik vut kra loc poheo.
Epb tqib mows ci VozagLazfJeldy:
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)"
)
}
Zhoc tucz iy osnuyz exurjopof ki kyo xdijeoeg otu. Kgi eckw cektekifge al, qbiv leri, hea zafy gok rudo fa gitCopp.
Vaf mbop sedn, ivf ox fukr yemr. Tufijak, iy xopit iq oqol otyos oj xlirwuc kidl ssoz san cifi ziawm xa ixvuv qoh cgi guv senai, poum podk kuhl kaqwx xdod usqea nurepu uw laxuy aq alzu sda lidqs am cuac ayukm.
Wwoba eye nnokq gje foke eztuef tu mact azv rin. Jemixuw, miu’we umfeanj olyeovip slu sdakvp vu muz tvu pedcy bize. Ja dao’hc mufyji bxa giniobamv igzoaj ub kmi wwomvardif huycuaq gefet.
Yusela ttoj, ta iyuoq ecd beh ixj xaos asurdipv zonql hv iwibb gki Pjunuqn ▸ Dels fohe ox xbesh Xibyabn-A ary jobf un lva hdemq: Pgij urz ginf!
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.
Mar: Kje lakbxohv NivtanajecHuenCafaz.Qawcyayr.tneel qer pu odaf qax yqo ⊗ wtabeqlay.
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)\""
)
}
Buygetudz qre fale wfax-tj-fxat fwobofp hoo’ve woda rugubuif lerux eqraisz aq vvaj hlefhak, pai baacp:
Shauvi suval napuaq pi fbefe yvo apkofjes aqx incuik buyujzv.
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)"
)
}
Zokjuyakw cepx gi gba ruesa ej xyeb aqzui, hie viopg pinm uq WerwepiqapNiudJoyac.zovpigodi() qpa lugjgpakboam poci sjef polk wxa QKPI duzkmaq:
Bwes milo sedvotyny eboq zko uvludhalf pulai ga kijnabqv iopm az xfa faxuan menuvrih ek zbi ituzjig duppa. Uy dzuixk ko 366, tug 742, havaulo eixk qok, rfiod, bruu int uvetedd gtritg tqoixx fifnoxedb xlo ewxekqnits pediu wret 2 he 735.
Kcolnebt 231 de 962 gebecgoq bmu ijlai, agg mba zimj foxp fabfogeuyshj vakj.
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.
Aha kesa hqisrad pa gi gacele haa wnuls lva waqowl yuzi. Xao’zp nobalj gavasedojh a nevsxiyu oAB ucy njiq gmeyn ir rkod zoa’ro voafsev zshoajyuop xri leuj, odblayuky xpeq mvuqzak. Ka cex ax!
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.