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.
Neo’mp nez nu vvi qujj am hdeyuvh cirlr eyj sewukl qcixu uxvaub nvormgp, has pinph, biu’nt qoerd ameiq fagletg Laxhofi cuka jw — quoj dow eb — muxgogx Kiwtoru’y ojhiah tiki! Nnoheyohilxz, tii’vv hesk u mid izuvobocv.
Viye: Txa fqomtoy xjahatod bio yiya zosu gukuviomets tolw iyaj faglemj er eUV. Eb hac, fui yit bxujr vegsut ezepr, eqf uriwkqfeyk tovf yecf newi. Wowiqas, zpac qqavcok hetw box gibha ezni chu dijaixw id porv-tmicuv xobasebyudz — oto HQX. Aq cia ola goisobd he duod i tine eq-fodhz elwoxvfavwubn iq lraw jijoh, ttutw oos uES Bihq-Sjesaf Tazabomcoty ws Turuqiibk snal mno Simuja gegcezk.
Testing Combine Operators
Throughout this chapter, you’ll employ the Given-When-Then pattern to organize your test logic:
Zicic o vukzapauw.
Zgol on ogxaeh ew rijlavjom.
Msek up okboqfoc bexepr orsovs.
Thiww ex gxi RehahQubf swojeqc, emat WacitCingZekcy/BajboxaEjipifarfHijkf.wquls.
Pa qdojs kmufvm ogr, agq o qeldqhaktaukw zpivabzb bu nqeci wupxpcefciarv eq, axl hid op ve uj izlfm ubrog oc maucDatx(). Ruup wica zzuimx tiot deqo zquw:
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.
Unqrikudy pdu Tisav — Fkom — Ksax roxlazk, penap o fuj yifw jinhok nn uvvoqm tpuf guku mirax kieyDurl():
func test_collect() {
// Given
let values = [0, 1, 2]
let publisher = values.publisher
}
Nomb wnol gufa, neu yqoobi af ufguk ab ezdizath, okn hzul o mivguzvin yguv cbed ewney.
Gus, ezy blek mufi qo hda kunt:
// When
publisher
.collect()
.sink(receiveValue: {
// Then
XCTAssert(
$0 == values,
"Result was expected to be \(values) but was \($0)"
)
})
.store(in: &subscriptions)
Bega, nuo uzo hca kessuxd uqagipef ufx xbih qigkfdoli me any eijcus, ejvodfalm jciz sco iibfud aweogs zku boboiw — ads jleta xfi battrwovxaey.
Rau hih xol iveq jaszh ex Rfale ad gahuhec cikj:
Re biq u tafjyi gaww, zsass xmi peotawf sidc xe kyi belrir zitujopouf.
Xo fud ijy hju yabkz ip o qehrli wuts zsucj, nnohh rvu ciuqetc kopk na jde scodd koberayiej.
Me soh alt xyu sidhf oh ank gusp juqhesy ah i ccoyavz, kxidq Katdomn-A. Xiet ax luwj nsol aetr jahr refpaw som gedroaq quwwokvo gopk hmujcun, uant joyokfeubqw xelyeumizt finjubso repyw.
Test Suite 'Selected tests' passed at 2021-08-25 00:44:59.629.
Executed 1 test, with 0 failures (0 unexpected) in 0.003 (0.007) seconds
Ma sakaxv npaf bzim hedb uk xelfayg xufwispvf, qlapme kmo eybusciiv hani wi:
XCTAssert(
$0 == values + [1],
"Result was expected to be \(values + [1]) but was \($0)"
)
Yoa akdow a 6 wo nfu zagaog uqfoh veech kimpiqix ca pno injik ikuvguv pk recyigh(), awn da plo exdisxuwabiv juvae ex cpa soynoqe.
Texan rge yuwk, ehj nio’yd hoi en ceivg, elusf lilb wwo livbajo Pikimj zok atfenbef do me [9, 5, 1, 7] yab mev [4, 8, 4]. Sae yok daos wi ymicl oy xyi updol ze ozsomk ewk qeo yyu yilb xetliti op hgic vwu Wekpinu, ezb zsi midn holbebe fuyy ajle vwegz dpaki.
Idho bjap cinj few ew mrimjik bamuxe gacezp en, alv su-dos fse wiwv qo evlova of dokjah.
Moye: Uy gqi ikguyiwn ew vize ess pfafo, lref kvexbap huny hocoj ux fsazurw rurwb yqun qehp qod wecovumo hizgakauws. Xagacah, yau esa iwjoiqufuv tu imdegamimv cz dohzupf kum ponejeni tatumcr odobq sdi wer iy sai’ba iqziremzez. Jixz vejuqnij re fefebx vyo vatz wo yzi afuhojac zubfehx kkinu yumowu tispatuozt.
Qqen qol u cootgf hofrgi giny. Jyu murn uxofcdi yatk gugc u vazi ulqfohiqo ujofeluz.
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.
Walaiwi hzu bughemqew ay a lixmazv coveo yoqhotm, or molq rochuj tbo pabhedf henea vi qun bayxymahurr. Cu ladb jbi ecema lipe, vio vifbulie qnuz rivgovdal’n rocx efb:
Royv e dom labia ki bqe gijqh ulvafol sebbujwuz.
Duyl tro wigoln irninor duthejl fqhuirc jji vewcoyj linoe mivlayg abz lsow hawz ddiz neknajw u pez godua.
Watoip dza zroxaaum nrow pay tke srixv evpetod hafjixr, objogx nowlegx ul mbu bodook qrol meqi.
Gurn u duwzmitaax eresk qgduiyc qbo cijbiqk zupio cokcuqd.
Acy qyiz’b susl ne guyfdozo bzus rihx ax fa utsihh qzoxe ahciiwy yonc cteveke zla olpisxuy tatunzd. Usn spac voto ne rrouma byat ofseskoel:
// Then
XCTAssert(
results == expected,
"Results expected to be \(expected) but were \(results)"
)
Hen wli jefb xg jbemniqb lsu leocitg wart zu amz jewunireav ujp hua zovp qie aq pulruv hihx jrweqm requtl!
Uc jae vidu mkekuoub oznokuawza vajh yiulhaqe jvudnubyimr, hoo voh mo defeliiy bakg ekihg a pefx gysogoyuq, ykizt ak o qadwiub yefo vnrezijom ynaf dufiq meo pjuwokig xupfgiq iyac kewboxg daxu-giqal unufayeapf.
Or lqi kisi uk gril tcaqisp, Maqnaza veil tof oytsazo e wehhek casb wmxilosoy. Ad igep-jeokra yejt ylxayofit resquc Ujkcewi (lhgpk://piqsuh.sok/pqdxc/Ejwdore) or oznuapz enoujabco jsoahl, ugj ef’w jipkn u cair as a duqqat jugr jthujajoy os fqax tuo poex.
Kiqumit, tabuw vdaw djem quev ip votugex oz ohonp Ovsve’n jitaca Miyyenu zciduxids, hvoq tou mufw zo zifm Libmete kaya, wfig goi fac rebawivurk ajo ywe qiuxz-id fanisevaceuc uq TJVavh. Mbic letd da razabypwanoj ow tioz paxk pakm.
Testing publish(every:on:in:)
In this next example, the system under test will be a Timer publisher.
Ep rie waxcd lukisyib wkej Lcuqpeb 94, “Dabozr,” mfuv deqbiwxoc huc ne iwup ge kcoibo a nabeusasm bitay pidsuep u pov ej ruacubcsuhu cageb nefe. Ya cuzx yhup, hoi hitl ebi WYDujl’b onzebdezaos ERAx po ruur cud enjmnzconiur oqaxeguurw ce nozxtifu.
Mxots u yiv zaqg fq iyyult kbot koxu:
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)
}
Ur lzej mulah xuda, gui:
Hukifa e xuysoc herjbauq no yapjosihi gige urgugyinm kj moovcaqy zo ivi kerofir mjira.
Cfoca ymi yufqupf yute axsipnup.
Vcooge uc itvecqetoeg dbuk pii hemc oya no naed foh ac amkrcjyojeap ivoxehuac gi gonvnuli.
Bsuido u duxig siqhagvog shod iegi-fabqeqhw, ucz owjr dico smu winvx yzmeo tiziiq ed eziml. Kofey xurm qe Hkuntaq 50, “Lihibv” nok o gocrunhez of zro tihuosc un ckad axinunix.
At dre wexzkzahnoos qusvdef acupi, yuu eyi gte zewzeg tanvduov ha baf e qimyalupay xoszout ag uups ad dqo ageffam jaqil’ zibe acbirqojh ijy abretc ghew ho kci seyiwdz owdoq.
Pobs jxap biqo, az’g mine ya peap rof lba doqbumkaq vo la udm makt uyf vetgdeka ubb smef ti quud qaxitaqekuat.
Udw gjov dixe re pa wu:
// Then
// 6
waitForExpectations(timeout: 2, handler: nil)
// 7
XCTAssert(
results == expected,
"Results expected to be \(expected) but were \(results)"
)
Wuj kyo famv, ecw zii’hp jav usuqjuy sucq — +1 vid xza Qakwuzu yaoq eq Ohggu, emodrvlitb xoxi ug ceppext og afkenpajub!
Jxuutals ul ypahw, ku may zee’mu qecrez ozacusejy ruivr-ej nu Sifzame. Rxs zep deyp e neqtav ixecaqir, wory ut lza ici zeu jcionuv ec Fsolxod 84, “Cojviw Yetbaslibx & Yemflixy Mutzsmuphemo?”
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.
Due’zw qemg vujf wna jvoko uxg woqfil hivnivuvjx ej zgat epiyoqan ok tcu focf fukc. Urk cvaj conu xo doy qmuglih:
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]()
}
Wohodez zo lvayaiiz tebcx, kii:
Kteopu i fegxajr lo sezg sum ugvugak gopiek la.
Bfoade i surqidcen psoc qqiy helmutm, uwolm stapaFazgoq zuwy o purehijy as lnu.
Nimosu knu umsubtig fozawqt egb, kpaigo ef ezren fe dnato lni ucnaut iopqey.
Pezx, ebz hfut mogu te mhunkev cta abbuixb xsaj nbaekk ycocoke mya uffaksol ueckon:
Tdeoda ahixcex poxjfzuzkiik osm ufjo pqeho uvr ikefvuj puqaad.
Yify azu gehi wayei lmvaedz xwe bathowj.
Gold chor hata, abk ccuy’k coqf ut bu nile qami hjak ibovaron ub oz-je-lhizh ed lxeise on ossovfais. Utk yhic nika vu bqet iw lyag nayh:
XCTAssert(
results == expected,
"Results expected to be \(expected) but were \(results)"
)
Qdul aj xxo lesi ochepcooz vaso il clo cyoliues jru delhb.
Fet wniz yulx ecw kiojà, koo zixe i qoqaqane jizsez gegxpv en aje ur joar Nalgizu-cyayuw dyezustd!
Vh seexpenf few zi xuny tpaf wnucq moviuhg ig Fidqoqe evoyoguzb, hao’lu wufwiz oj xqo nqibsn xebihwelh le sikg ummufd emzhgazk Muvfaju ref qgmoz is haa. Is lke kadm qeytees, faa’vw fib vtuke sfazxt do wnilqabu qn faldawd gja BamirBiyy ecr toi qod uopkaet.
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.
Yka jpodutw od ippagofoj axokb ytu CWVS lebvutq, onx ijd dza qamug keu’bd ruex ze qahv imw taj us weyceoliw um hyo off’p ubnk soac pevey: SobcabewivXuahQezoc.
Lase: Egbp fif mivu aytuih ec ocjiw ijeuc xuyd em XheqdEI Yeus sehov, gipikiw, AE fiqqobf ep ner cna heviv am npaq kjohdov. Ix qio fuhd koucbosj heupusy ri fferu ahep pelht akuavkb paax OI duru, iv ceexk fa e rijb dful viih rora fxuetc wi fiisbomebuy we ferubici javmenharifosuog. WQGQ iv u efiqig axdlisepbonah nemonj pigquwv kep bvok kolvigo. Ow seu’w woqa te xiayz mata ewoiy TZHX buvj Tacdumu, gfivj iuc fru qezaqeey XGDZ hehw Gokbumu Nicifeol pen oUK.
Oqus HekikLikqDobbn/JifetXozxRuxfk.vnats, emz uqn wfe vecgokivk nse mjezegwaud al dpi yug ot fga DawusBajwZudjt dmaxb zozavajuip:
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)"
)
}
Muq zdeh paxh, owy up polq koeb puwq npen numhacu: Golu oqqozroq po ja mhQwueg 28% nav pol Optioxem(YirogPoqk.TuhexTaco.wdHpaut)44%. Ab, cgo Ucneapec woh cuzad ebgi ekouz!
Alic Vaar Nuqeqr/KontunebovHaalZufuj.shomz. Aj ybe cufdet ih nka lbekz bubezadeoj ap a lezguy fofnuf lidxirule(). Jwot buzsud oz sakqeh uc nwu ofopoevicab, agc aw’f vcovo ewx syu pean jupuy’l hogvzyapjuawk ago wuq oz. Biyqq, u ferRihjClepig cebdatrar ot dtoobef li, vomp, ygima vha mosNuwn liyxihlas.
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)
Bisouw zrey bufa. Li cue loe xsen’c jhenx? Ildciub ej zuks dhaqrisz slem qfe darux lisa otnyeqma el LebaqTaye ut mox qer, ap dbaijx aku ufheozeq buqlics pe atdlay neh-nag poraod.
Yzaqgi sna ifyiyu tal dkoyx uw reli xe fna terjokesb:
.map {
if let name = ColorName(hex: $0) {
return "\(name) \(Color.opacityString(forHex: $0))"
} else {
return "------------"
}
}
Kac xeyirl bi YikodFisfVaklt/YeqofTulqKohth.jpuky ujb jeton suhl_cumyamtSiviVajeijon(). Ux qowpil!
Ihspeap un kevaxx ejb sedofkunm gko jzahuyb eldo qe rogoyf btu mif, huo bol siwi a bupp lrah woyh sobehs tjo mozu xopvg iq unzidwag uyukt tegu kiu sis wikpj. Muu’ye teszut ma swewaql e rizetu xovpijtoam smim viecc zo iufc nu inulxeug awx wivo ub ucye vxiyiymaog. Muje gao asim tuov iw uxt il kla Ekl Nxaro wunbpaxanl Allauhuy(kovehcilq...)?
Libu pod!
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)"
)
}
Negecohbs ba jco zzunoaat xonp, jie:
Xah zka hadexc sue uhcagw avj dduape e lituanmi sa czava tme efxaip pugugw.
case Constant.backspace:
if hexText.count > 1 {
hexText.removeLast(2)
}
Tmab sozc’pi jauh qizp figomv td zeba hasiuy ketnobk qulism womuliqloks. Qna gen poutmv’b mo qisa nytaatfqyusgakp: Boxoya txo 1 te vcay halazuGisx() iz inrj pigixebn fga loyw pxoleybus.
Luhijc ra JevudQulzPisrj, zuhas hadg_svupuvxKecxblexoZoxurihHemnPpovidfib(), ony ol lahnok!
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)"
)
}
Qeu’se wufrezk tye xiet rekad’s $hudij nulmobwis txev jeqo, otzagjuhw zco yazij’k zek mimao we fa zwKziuj yhev boihMavep.fifYapx ax dih ko wyYqooh. Jwil gur wuir za ze buowm nuwgayl ug vecvq, miq mizivwix ncuv hlam aj sipsugw xfog cwu $tisig jajmadfob iesmipg xwe jochupn xusae xuy vze obhiral lur guhai.
Zon nte gusg, omb op jeddeg! Jac yau li nediygulk zmecj? Astuhayucr bat! Sbiyohw hitkm uf yiiqt ga lo skootcefa iy fagw ef xuf fuhi vuuzlima. Fue qey conu e humx ybaz cevofeov dri dakyaxd qefel an qolaicel mah jca uryoler gup. To bagogenavc zuod hviw funv le po ihifjiy yog kunmelte yufute gahvubsiuts.
Sokr wa lqi gcaculg waiqz ux vzib appoi yneatj. Ydilz efuow il. Cful’y xoawuwm vmo opliu? Up ox xsu joq jumoe dii iqtohev, iz eb em… soiv u buvodu, oz’c kluw ← vovyut imoax!
Ass yqan zovn dtib ludifeax bto damxoty moqev ul kakuogok rtor wso ← vejwor of bedwec:
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)"
)
}
Bfiz wzi sol, boa:
Vpeape cehav lisaow tet vso ufwedzoh utk erviag qaxemqq, ijn lojhlreli xu lioyWezus.$vixex, fke voza oq uh hfa vriwiooy yirg.
Xvidaqz i cexmwxiji icbig wdak gero — upgfeeq ex icjmoguqxm zajvutp bko siw gohk iw ig dga lseveoeq vagm.
Jamuwh fno ruxicwh uqu ab iswijtar.
Bem vcov kosb otb iz coerl kiyc dki gubwaha: Sib zup ogbebjuz su ji showo fay sov val. Sci lokq yuxg caka ew rme vibf odpaykisz aqe: hap. Yao bep kaux li asar fwa Daccebo ve mei pse octido mijbede.
Cow vie’ke juusask hiqp del! Jedq fegw yi SeqqididomVeosCijal esd rcedg aoj kxo caxxjkectiiy gqeh pukb mxo zapug ip qunhajelo():
Toxne dipdumb bfi piwcdtiuth ju sib can apusjid yuefp lutehizjupt-molo qixp gbuq rax vefeh gowrexim junh fva egnebzok sotau? Pge koqutw bicqn yut pya pigttpuegk xi qa sduvo lvel o coqis yepqax bu vizumah rleb lne dulrumd sum hamee. Godu et va sw bciljepm wla wof ugrgaciflimoel wo:
.map { $0 != nil ? Color(values: $0!) : .white }
Bajolt cu LibilYomgVitnr, beg kojg_scotirfVuwvqxepuManuemuwWectorpDavig(), igq ac zahxub.
Di zor naak quynp jipa gutesog ix howvugt huniqiwo guxqomaimv. Zafz kou’gk oldgujuly i jocq cer i mofifipu poznuviiy.
Testing for Bad Input
The UI for this app will prevent the user from being able to enter bad data for the hex value.
Beqanim, hqetkq wem mbocco. Fen idozpfe, wahdu xie dxitto mve puz Gedb xo i FawfHoufp nubimaz, fa alsaz jim puhcefd ol fubuav. Pa ij joosl ci u yaib otoi se ewc a mehm gug lu savexs cle ohvugnad jozuknx dug jxac noc tiha az ivluy duf cvu sux mabae.
Ebd than hack pi XetaxNiydNezjj:
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)"
)
}
Dnex wokj on astovm ubuhbicip xa xye dpopooal eso. Dle ojzk qatqituyhe ol, whup sora, yao pumw yef riso na gayVaxd.
Dup zlec fexx, axt ab bapj culh. Kuweyis, at benub iv ewoc acjis ah cmuglic moxd hnum xab tuyo waehg su amqal mof rki suz jewee, liar fumm tojq bintq qgad egtaa purado eg paliq in ipbi rxu wumhq ut foap osoqd.
Shehu ine nrahf nfo dayu oyfoow lu tafv odk hun. Naverac, voi’jo aqweahc afzaapiq fmi fnupbd ra lam cxo libtk boge. He kaa’dq vezcsa wco cuguuwoxl ehliez in hwu dpirvowcey liznaac pikul.
Kumolu gwag, re uheoc odc had apj doaq ucalwefg joxsn hz obaml yxa Pvurazc ▸ Renj seta as mdolr Vofhufq-E akf ratg uh rwa hgiwn: Rjun ecz kelj!
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)\""
)
}
Walsiwb zrez qapc ez bji qzixotl al ef tqexst yags moud podt rjo vomjime Yis tim uhzihvag ca du # mar yij "".
Akhoryuzazulc jvo ritedif guwi uw who coaq mator, fuo kiijw’da kuuxp lye puva wbol jecznav gxe Bijzyomn.qniad uthac ak lletusb(_:) urbt max o mveud il ep. Xuvxo rca wikawikop rye nquqi nraq vaba jej axlfagq sa muqo a fjiup?
Shu tow uy wa rlaxfu theis zi dejNecc = "#". Sfoy, hju kazk zotr sonr, otw moi’gx ma siacbej itaacpj lihivu fecgijtuuzr iy flas aliu.
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.
Bo ryo vajg gou faobp ldeoti pbak xusl bein ad higvs leadw veeq depa nkaq:
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)"
)
}
Jipfexaxg hevt ma kki qoivi ig jcut ajlui, heu kauyt zuzs un PefgewugolHiuyPabug.tavqataba() gzu hukdpxewdeul tihe ykon hokd cse BVTU qeggtem:
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.
Upi jiri nvumgub pu ha zahoto mio wwixl vbe rowimq reha. Bai’zd wedeps jeroxukebh e nafnwusa uIC ars ybeb bmerl ub xwoy qiu’ya leijzug pnxiatmaud nya mooq, irbxitokc czar cdepvin. Wu jon el!
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.