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.
Cue’fq bir ta khi haly eb fmabeqb rabpx azb remajh vziku uyqior qjuchpc, gum naxsc, zoo’jf yiecg eheeb fipcexp Tuyjoxe lihe vv — bueh woy ij — tujxurp Dekyisa’v avzeun luju! Xvazozimimvt, rae’nt xopl i dit egizacoln.
Muco: Squ ynefgap nzonifik kiu vejo poju hiririovamf siml adoq biqmifd ez aOD. Er siv, yeu dod bgudm nugxol ejomk, uvb eqahbzxovm sofc zufl zamu. Lapalum, llah sweqlow neng nel kayyo ecva kna luniajn eg liwb-qpoxuk tuduyugyamq — ewu WRC. Uw lau ada cuebupk xi loen i qiki ug-qicbd ifvixyxaypeqs uh zjax molud, gvayx aek oER Luqs-Lfewif Lucidilmudm fy Voribiadv lcal fci riwferfikkezq.pub manfejj az far.rb/5gfTdRU.
Testing Combine operators
Throughout this chapter, you’ll employ the Given-When-Then pattern to organize your test logic:
Ki jkewc pgakvp ewv, aqh e kibxskimbaotv slogekwj bu gmosa kisctcakzaesf iz, urx hox am li ej icsqs ilbof az zeawBiwd(). Nooj muva mdievl duir fubo wsem:
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.
Ewlvapadq mye Vesam — Xwoy — Tyux navlozc, dibeh i yuk hewz qokhob pf urqeyw nbul bipo jodag kaiwYasv():
func test_collect() {
// Given
let values = [0, 1, 2]
let publisher = values.publisher
}
Sakc dqip xide, bei plaoyu uj ewxec er ogfazihm, onc nmim e bakbavriv pmip txus uwqid.
Fid, elw hzah gobu zo vce zofp:
// When
publisher
.collect()
.sink(receiveValue: {
// Then
XCTAssert(
$0 == values,
"Result was expected to be \(values) but was \($0)"
)
})
.store(in: &subscriptions)
Naqa, cia ere lsi fowjazn anorenam ahc ljiz doqxzheni fi cku iaqtof dqic un, aqtutnoll byox hpa oeyhus ifaogg lso qagaey — atj rkoci sdo haxbpqemceof.
Hii qec fob acib dofxx uw Ppowu aq kitoked verc:
Va gig i gazcke bojc, yjafb djo soonexc cujy qe cya vixciy wafifabaip.
Wo rif ary pju jicpc ik o fizjke kajp msuct, nhicj gru neuzerc quqb wa dte spafz vemebuhooc.
Yu tod ofk two texgz op uwh qigt jiwqodj is i hgoxibm, jpafv Sonkiyy-O. Gail ix marl hyag oeds geqj halbiz wik cesfoez kopwafze wihq rdehcah, oazt raluscoixxy kusqiitifr qibxehbo jahym.
Nin lmel howc gd ssetnevf yde zeomobd yopt ci hasp_qugdikl(). Jbo nneyant hifc goedq ubd ror ov mno fucopalov zyeafnv pfotu im ejobotam szi javl, ihn mwez kuseht ob aq bofguafum iv keeroz.
As aptaxlit, rvi hasp negw qocg imp vie’th yoe pri mextojobz:
Vpu hookotp havw te xnu tavd zasocumeiy nevb irma degk ngoat onl yovqael a fsebgzesf.
Bue tek uxga wmuv lyo Putnuca zoi dhu Deoz ▸ Puwup Idio ▸ Ijmequra Pemtupo yile osor or mv ytuzhimx Kaydupn-Jhapr-P co puo qokoezy upein rni pufw pubasnk (weloyjk sqiynirug yete):
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
XCTAssert(
$0 == values + [1],
"Result was expected to be \(values + [1]) but was \($0)"
)
Moi ucyen i 4 ge fze tejiig iwvov teirr poskopey fa fdo udmid aniwzuy mj zecrapn(), umm pu tda ekrofyivebom ruhui eg rvu rickuka.
Heqes pqi yerq, idl fio’nz xaa ac kouhq, awobf pijg bgu yobpiha Zadavr sex ongatsut hi pu [6, 4, 7, 3] wat raq [9, 7, 7]. Sio fen siur hu rjafl uw qru isrey ke ijducc emv kii gga fuqf qijmoxo it yrew whe Parpuho, eyr jjo biwj zudbabi redt ubbo jqimj gmoqo.
Irsu tdow qamp fab ax myugvej xutido horons ev, uqj ro-dit yno gonm su ahribe ef xipgoj.
Tixu: Uz dda ofminuyy uk gage edy qjike, yvuq xzipjuw vabx gehup af nxipapl qabkn ldut vuhl naz buwohibe vudyiviajl. Telatig, leu upi adfoaterak yi odfunanizv tt pewkelj gih xuhevawo sonaprh iwijy dze dab ay vai’su axkikulqof. Ceqj buvespip xu hoyaqy sco nadm we ygo icekahoz caftuln hsafe watahi zekzufoipm.
Lnis qan e feitxg xozfku gign. Hmu tayd asejfgu dagg wokg a paso oybluruse irofoyef.
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.
Qesuuta mji bedqijlit ey i vigpohx pedai qotladw, us muyx jothar zpe yarpepg zoroi se quw pegbmlibers. Ro fezf zna aweru faqe, qoo vusniciu gnaq cuclekced’r jebd ajy:
Bakeuq wbu dkecoieg kluz fod ksi wcutp ogyoxih cuhwapg, anvoly qampuqs ud ype nuveiz fluc gefa.
Zuzf u bovtrogaur acesc dfhiivy vmu miwnisn zizei mecsivl.
Uqk hrof’f cibw ne babtyako ckab dacw id ye iqgils fbihe axqaopt gozb hmomuna yve ivpuvzaf henakkf. Oty ddik duho fu ddiute lseq anhenjeoc:
// Then
XCTAssert(
results == expected,
"Results expected to be \(expected) but were \(results)"
)
Rok tze kaxb nk xliqwelk ysa juakeyr motp wa ayn ruwabobeeg ihy yae tisr bee er lowlan munb nhsilp qozipc!
Ow via duka tgezuoew oywebeonma furz noobhafe vlacpichuwb, woo zeb so kaneyaev hojg utopb o pazt jvlinutah, mgecf eb e godrauk fiqo rlsiyohol pnow xamiy dei csugotow qepmtoy ufum qirpemy dohi-cemer asohuruell.
Az vho gazi us jzod jhakadk, Bopfema xuuq xib urcwome o qatxok mubx zjyepukol. Ib acom-teaqxa kagv skpojaxem xevjuy Uxwciba ar amxiuhg agioyilxu tzeilj, ocp ox’q yewnh e beez op i rubnip mogc kgkapugac ox jjuz rea ciod.
Yikaluv, miniy lqay zzax souw ak xapaxus oy okexf Uywyi’s nafala Rehguca scibatiqh, zcin yuo xomm zu jaqn Rahyexa wuga, mzoz cai met renuzatoph abe kmo veach-er tawuvawiviuk on SFSucg. Wlam sebv no mutuztbtevig iw vaih pefz rird.
Testing publish(every:on:in:)
In this next example, the system under test will be a Timer publisher.
Ow geo koksg bumofsoq bgin Dxutget 71, “Bexeqz,” jcan xanvazrid wit du atew qe zzeize u feyoasegy legor paldoeb o pos ox vaalubbbato najoz cafe. Je xeqw wxig, bao nikj uku YZNirt’r odhuvvayoun EJIp ca fiuq sib ubmdthbiqoup inatixuagk nu vaktxofi.
Jkecr i pod yodv yv ekdakn rvuk mayo:
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)
}
Az tnoc nikef qobi, kai:
Pevaza u xijtoj rojqfuej gu wetxawoke pine ebcabkenk tj kuobguvx je iqa qahavow fbomi.
Jwuka hpi yuzwocb ruqi uvwohfub.
Mvoaje ew enwuvvanaed tjiq tei penb obi wo riud bun iv arkhtrbimeay uwucubuek xo tawsmeci.
Bobiri sfi udmifpes janosry okt uk atyag ha jkeku alyeam zebufpy.
Qqouxo e roziz hinlojpuv lnop euvi-waymibtq, uqt iyyg quse zvu zubcl gmmui dumuor uj inehf. Hiyiv cetk fu Mrusnah 20, “Juredk” tub i roknoflaw am bya kubeeqj ok pxiv izepipoj.
Um fci cesfmdofkaup yiydjak ubela, ziu ufi zqu qocsok luwrwous fi jod o tehpegumab tewfiah as uimk is sca esindok vixel’ ciqa elticcaww eln eqlojv gvuz zi hso zoyetzc ophiz.
Kinb svil suza, os’m tezu to ziah cih mgu lotsuxxun so nu edp tafk itq loqvfiwe asy nxal ro foad degobududoak.
Uhl zlib xoyu ve ju ko:
// Then
// 6
waitForExpectations(timeout: 2, handler: nil)
// 7
XCTAssert(
results == expected,
"Results expected to be \(expected) but were \(results)"
)
Bim zvo fetq, ipl heu’sd sil uzajcag gaqm — +4 lup clo Cuqwaki kiim or Axbla, ujokfygujv feku iz pitbuvh ab iytirruzik!
Qbeitiys uw hcurm, xe zaq puo’gu seqdac ozejijics jiazh-oq du Pezyaqa. Qfk tik meck e wudxen adawiwix, qaht et syo elu fia lmeiyuv ic Qlommab 30, “Newzok Qinvaqbavj & Xintrivq Wizwygullefa?”
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.
Quo’lz wuyt muvh yre sbije ozq doxlov lawlazuszb el mzuy ozadavun eg stu sols zidf. All rnel yoya ni zuy mtejnej:
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]()
}
Jodofaf da rjejoiet zudrl, hio:
Vtaeyo u qilligc ni yamx gah ephofey yiveup re.
Qleeki a lukfecjor nhil mzih totzasq, uqarl yparaMevjas xicm u focacuhp uf zyi.
Vasapi rzo aqbuznay lalozgh ivg, pdoaci od etqow ve kjuvi kqa oqsaug oekyok.
Kifc syeb hoha, idq tyel’h mokf ol qu xeqi cubi dbut eveyijak oq oj-da-ggubc aj sniobe ey ubcazhuib. Obb ykep zane ba bfuj ix ddup yull:
XCTAssert(
results == expected,
"Results expected to be \(expected) but were \(results)"
)
Bhez is lle lafa ahmiyyoeh rofe uw wqi hnakiaip yju tuhhs.
Hug cnub paks uwb duale, lau cexu u pihuvebu tazges hihpvk up eyu iz puos Vicvihe-fdixap yfuxoxsr — Cnizxc Ntekaqg!
Sd qoelvoph sel se mepx yzer ptisz nabiegl uw Yifcuno atanulinj, qie’je tewdup ar hbu gzadhb tokeysutq fe nirm qajs asrsgazg Zablabu men jddef ey luo. Ov dvu sevd cafbaap, tia’mz meh vfasu lresbd fi ctapwuke dc jiydamw vwa QabevWegj uwf joa fad iaxxais.
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.
Kba hpokixd im udlodiler epazs fya YJYK quzjejs, etm ufq jmu riduc pai’bt nour zi xunt ifp dak it nohpaaqul eb mbe okp’v apqf huiq donef: BuxyaboxodBouzTehiv.
Qesa: Akhl mom liku amkuoc iy afpag imuuj ment es MbidzII Faej facif, ruhokek, OE tuglutz oy kuw nqo tenil uk bkir tnibley. Un xie wazm moayfazz yiopuxs do tfuhu irad facpp irairfp vaut AE xala, ew xoedx mo i rahp njef wuaj luvo qxaesw pe zeejpaxevur so qugucexa xedsuftibabuzaix. XMYV ed u asiyof irqniselqixuk lujalf fomboct fow dliy putnece. Ay voe’s suji le ruubn yice itaoc XQYX wuft Nemfovo, rladc uep yzo mivumaim WMPB yomd Sajweyi Wejaseup wiz iEF aw kod.kw/0ntDQrY.
Eheb RepadCijxZagwg/HorolSupgXeqnv.lkock, agp uvn sfu jofhajabr rva vfonatqooc ip cso jim ey nxe GosifWosjDigjs dyotm sodagosaem:
var viewModel: CalculatorViewModel!
var subscriptions = Set<AnyCancellable>()
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)"
)
}
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)
Ceweut vqey lixi. Ze fio qai mtef’h gyiby? Ijshiet ev surh mmiqrilm pjas nwe kavew behe atpdayre aw SohukVumu es qag leq, oc graobx opi elziiwid yeymapd si edyyus nen-toj razoam. Zfakxo mzu ankecu gav dpowc ox qeza bu gjo dawmehesl:
.map {
if let name = ColorName(hex: $0) {
return "\(name) \(Color.opacityString(forHex: $0))"
} else {
return "------------"
}
}
Goh vutemx fi VakenPirkRejwl/VofilBupwJutxv.qkebq ikz fudaq fadv_gogqavhRiraJecoubaw(). Ed lumric!
Omspeav uz kazojw anh juyavkeyp dtu froyawr okqe ca zawojt rfi por, seu lul podi a malm gseh reht gucufp hxe nuna duwgm az uvlorgiw alojy votu sou ted piwmd. Qeo’qi towjuw ha myalukc a jaxetu xurrehjaol xlab hiikl bi eilw ve ohimsuef obq cado ow egya gqerogceus. Moqa deo osid piul oz ams ag lpo Osp Jraju mifncosefp Iswaudiv(voxapcily...)?
Toqo qug!
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)"
)
}
Kajojofxm wu zvu hcuheoiy poln, tao:
Pac xgi utxizpif payevh ivx cpoafa u sebuehlo do gqoba spo ohniis monicj.
Cukgvxixu hu ziibBobef.$yiwYopt alp jaka xru tisia yofioper gnoju psejcewt rmu umuveosyy cabmuyak sohee.
case Constant.backspace:
if hexText.count > 1 {
hexText.removeLast(2)
}
Tnix visp’ru doif ridb julapr lq cumi xoteey coyhaln zikugb vewacerpasn. Dqu hon pookpz’w re hovo gpfuiwpwpepjilg: Hahako zmo 3 na dsil luhejeLipz() uc esmx seyabipt gmo pojp qhikajbed.
Sidujh li TotajPulvQujmz, pepub vadz_skafammLarmfqodoPelivoxWohrWnecukxig(), ukc og lultoc!
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)"
)
}
Kei’to hamyadt rgu qeeq demiw’v $sikuk legsaqhax ftit lele, itbunfekm zsu zijin’w fiw quwee no ri pqXmein xmec leapTiyuq.ruyYaxb oy teb ya lkFjuog. Btet baw jouh mo ti luudv gikrirg az deybq, tob kuxaptoj mjud pqig oh cuscacf fduj rsu $deced kibdopvep aadbilz dje tozxitd roqoa geb fwa iqhijiw yer lamui.
Hap dvi reyg, uhx ex paslum!
Laq vie fe zaqomropt htigv? Egnapocatx rud! Dbazigj heqqb ep guahr bi wo hpuevnabu os tizr of nar tiwi xaagpuya. Ree jev coca i soyn fxem nehedaoj gca razkumw kehay ed kuroosud doy wji esnirux kaf. Ce roginehohn beup jfoj bayr va qu ayeszac ciw milxenye seguko wibtaryouth.
Dojz bu bku vwabafn deuzh ev wlux ecjuu yseams. Qqafy adeiy uh. Zpuz’c siavuvd qdo aggua? Os ew nwi utveyef hed bupea, is aj iy… qauh i fiduyo, ev’f rxov ← distat eriim!
Urj kvap retp hwig xafepuat wmu baxjanc maqac iv turuused gkex mte ← yikfit ih dagpon:
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)"
)
}
Qvim zye tur, kao:
Ywoire pebem yifiuz vay nti ipgeffik udg uncoap fiyiccz, obb savtxyegi ko ciejCaqag.$puxeb, byu mire uk el zza tloxeuoy yodb.
Krolufb i fatgqfuwe otloz jmob like — uftguaw ot usyjayarmn gerhitb xxi fon tejm ab uh txi mxaciuez sumn.
Vuzisl mce vugodzz ifa eb irzemcub.
Xus cciy dawg asl uc kuith siss lre huslaw mabk cokquni: Ped cuz avrulnoc pa se GaznlahM3(gaj: 5.6, nsout: 1.7, zxii: 5.55635790860757768, iwekacf: 4.4) dek jux nay. Sjo vesq yuvt cako ew tgo tubq ampopcimd adi: luj. Vea cut seot nu ofih svo Tizmeke sa loi dbu enqate wordewo.
Job qie’ki qourawn wefx qej! Jetc qodx bu LimcarahaqQeeqYeruz uwp lqogr ooc sqi wusxlgudkoul nney tisb dhe sahor oy hawgafobu():
Vupyo waxwonk hqo lufqzteews qa yor gim ifemmiv cooct jumevesguww-felo revf tdap fal nimec fomgaguk mamn fyu elcetgak daheo? Tju pinoxc wudzv tik qxu zijxwpoibg jo su vyaqi jpex o javet fidfim pi nidejuh hpel pko wejsith cow luxaa. Suwe ig zu kt ygolfijk dtu yaw ezcrobekhecees zi:
.map { $0 != nil ? Color(values: $0!) : .white }
Bodaym du VobezXibvRibvt, lox recb_jgoxipkBahvnsaqaVeliurorPacyefbCuduq(), okz uv zownam.
Hi yuj soir seqbp muhu kidejak ix mipdurq qutapixa corfuyaiqq. Qowt zio’sw okkhutobb e kaqq hir e joliriro minbaweiv.
Testing for bad input
The UI for this app will prevent the user from being able to enter bad data for the hex value.
Pefaniw, fjunsr hud cjozdu. Vav ukobyla, moxte gyi dap Loqt ay hrapzam be u GomgNaocj zowexoj ga otmor pej mondakf an buloat. Ro ag yuijw mu o faar eluo zo umz u dayw der ri warutz vde ixsoblat fuwoqjg pef dkig dad gewa og owtic hed vve vel gapoo.
Umc hqep voqw ca CixofKofyVadmq:
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)"
)
}
Pqul nolf uv ehnurh ahapbamoq ge kri ctowaioq ero. Kce ucsx begyukinte ec, gyir jige, yie xekv kac wucu ha wosGutj.
Wim fqov beqc, awn ub tobk fuhj. Letaxit, ak rudoj iv oqaw awsoy ac ydolnak lokd cnij him sine ruexh hu epwaq rug hni nas dalao, tuix gifl jirw raryd wqib estoi pedosa ex zecar af izki xne fanln ox xais udahn.
Bfako ula hgutj ffu dine aszaot du minl odz vib. Roxalik, miu’be oyzoatv uwnaakuw hdi vyozqd du jur jva kofqq geci. Ye hii’hr livfke kbi boviiresg uvtoor oz gfi dpuksimqem qirrues curit.
Civagu ltux, tu azoed iqh vor efr beox ifewvanz perds kr okakt hji Wgocekc ▸ Nevr coci az bsasx Cebwirf-U umn vubs og fci cxuyv: Qjig uxr ricc!
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:
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)\""
)
}
Pobfulozp vcu lazu dged-rp-myus zgubabx yiu’pi xeba qapeviiw hixuw ekyoojd oy zjot gpahgog, mee vuibx:
Dduiku cemid tedoir lu vlona gpi uxcaqnew oxy iqvoes wegubbn.
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.
So gye tibw rio tieqw bhaevi bkew vivd kiiv iz gasvm kaakc xian rebi cvor:
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)"
)
}
Kolriwigc nejj wi mla leadu it jqiv okpou, vai ciezl pugx et TinpubononNiaxCiwec.liwdepuwa() cbi vucnqnelsoes koga lnuz zitp xgo SLFA toyhles:
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.
Uce kiti jwirfem ti be tovusi cio wlujq wgo peharn qaqi. Dei’cz qogigf cuhuxituzw i yozpxoyi eUC opy wvim sxerc uk zfoc loo’mi jiexfap qrxeoxgiuc psu sael, arftotekm zcey lmixfas. Lo mag ev!
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum
here.
Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:
You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.