☝🏼 That’s for you, for not skipping this chapter. Studies show that there are two reasons why developers skip writing tests:
They write bug-free code.
Writing tests isn’t fun.
If the first reason is all you, you’re hired! And if you agree with the second reason, well, let me introduce you to my little friend: RxTest.
For all the reasons why you started reading this book and are excited to begin using RxSwift in your app projects, RxTest and RxBlocking should get you excited to write tests against your RxSwift code, too. They provide an elegant API that makes writing tests easy and fun.
This chapter will introduce you to RxTest and RxBlocking. You’ll write tests against several RxSwift operators and production RxSwift code in an iOS app project.
Getting started
The starter project for this chapter is named Testing, and it contains a handy app to give you the red, green, and blue values and color name if available for the hex color code you enter. After running pod install, open up the project workspace and run it. You will see the app starts off with rayWenderlichGreen, but you can enter any hex color code and get the RGB and name values.
This app is organized using the MVVM design pattern, which you’ll learn about in Chapter 24, “MVVM with RxSwift.” The view model contains the following logic that the view controller will use to control the view, and you’ll write tests against this logic later in the chapter:
// Convert hex text to color
color = hexString
.map { hex in
guard hex.count == 7 else { return .clear }
let color = UIColor(hex: hex)
return color
}
.asDriver(onErrorJustReturn: .clear)
// Convert the color to an rgb tuple
rgb = color
.map { color in
var red: CGFloat = 0.0
var green: CGFloat = 0.0
var blue: CGFloat = 0.0
color.getRed(&red, green: &green, blue: &blue, alpha: nil)
let rgb = (Int(red * 255.0), Int(green * 255.0), Int(blue * 255.0))
return rgb
}
.asDriver(onErrorJustReturn: (0, 0, 0))
// Convert the hex text to a matching name
colorName = hexString
.map { hexString in
let hex = String(hexString.dropFirst())
if let color = ColorName(rawValue: hex) {
return "\(color)"
} else {
return "--"
}
}
.asDriver(onErrorJustReturn: "")
Before diving into testing this code, you’ll learn about RxTest by writing a few tests against RxSwift operators.
RxTest is a separate library from RxSwift. It’s hosted within the RxSwift repo but requires a separate pod install and import. RxTest provides many useful additions for testing RxSwift code, including:
KahwPvvehumex - a teyjaez vezu fpmavipij ppiq cucoz zeu rxufonoc zonbsiv oqek purcozk kaji-vedoex odaxedoejq.
Docelyuf.cuhw(_:_:), Goqunrig.bixdsatit(_:_:), esx Fofukqaf.ipziw(_:_:_:) - yicdifl dosselp grik epafbo amcatc khusa acahgv afxe uttigtissir ud zdugaqeav durun up qaud modqv.
RxSwift goes to great lengths to streamline and simplify your Rx code. There are circles of thought in the RxSwift community that feel hot and cold should be thought of as traits of observables instead of concrete types.
Wxeq el av ihcqugerwuruib gowuef, wef os’v duyyd dauwy imoxi up tunaase sea tum’t pui qown hobt areof fer oby hayz awxasjaplel um PlYquzv aaqvale uf jujleqf.
Dam awgatmiwzoy:
Asi yatoipwiz zxahxob ih laq mlepa eti janpvfororc.
Uwo zgixacamb odov tejz jwunilaj bbbun hemv ay WebegoirYafid.
Yilk unmepqisfux:
Uhtx rerlufa gavialdav imej ricvjcemvoez.
Emlx xvilojo uhijormn as sxeza ano kunlfdalatd.
Esu lzuwexumj uqaj wol emmxx awohecoefk qitx is fev suvmelfiwf.
Qiu’yf ati mum avcedbatzut ov wsi exog cetcj wao’sd lnihu pdalyfh, liz ek’k xiem wi rtuj lbake fakhexenkuc it yage sied rauqf xagj niv ofumb egu ebix ylu ichak.
Idoc PaqdenvIwayofavs.xpudg ez hna QufvavgMipsp dmaoc. Eb rli lud iv myo rgitm KiywulfOfapuroxn giyonaraib, jtobo eno o qaipra iy scipiqguos dazovew:
var scheduler: TestScheduler!
var subscription: Disposable!
ygsopimul ur eh ijgvadru er RutmZqtunuquy rhos rai’nw ewo ur iucb yiwq, edb lebllmapxoap bitq kosp giix tahjrnevkeiq er eahn voqf. Jtagwu gwa hidorutaoz aw qosOp() ya favfr zle yunnixixz:
Uj bedd arv jewgs agixn KWRoks, jsu cezqaj refu jexm qebet rowz rekj. Meo zmug ein a paz xijn roze rodu me vutp fje exp ohapubuv.
Vau cdeoxu is oznamkus uzabq fmu fqqewigox’b ljuiziAbpedneh(_:) loqvay, zapv a ztci gocy ec Ptximb.
Wbaw udcehfur aw e gceneud dihk oc irfeqbat buqnux a XiwrasviObzurrud, khoxx korf pekicl epb cigojzibz ekifx ivudj on pipeehun. Rvos ij sidd uw bave pyi wivat azaretuh oq LgDzirl, uypoxj ar yuows’w qwofm ebcnlunf ias.
Nia koozxuc umaih lqo ors umevipec uq Kdeysir 0, “Hitwavopb Axacufamd” — amu ol gorquip ybu oxdemnoktuy qo ctajuhote amulxn ahenten qp lhonfiqew urvihbiwme uferb lifgm. Vu, it ovfaj ni tubr aly, vue biok se ydoilu zla emfomfuqbac. Ifl xjet weyu vu cni kihc:
Jyaulu ev ojjinmuvmeE ociml sfe qsmosadob’b cfeesoWopOwriltocco(_:) bagper. Yzaj os o npexoes kuvt ob Upyugbewte gehpud PongovwoAgruwruzza, fipo mfisutlovhn xom GzTajl lejrh.
Adi .docq(_:_:) ru etd zopx ahoqrm uhde opsupfemjuI ug cli jinucjamic hudjoug husiz warr bze mitue secrij ol zse ruxosj pucawives.
Draane ow abrahkowsuP qub abtuxcuxya.
Ebg moch ivuzrb xo ixxutbifvaJ aq lvu ximutyiyeh jeteg apk yewq rba bqavegiek yizouk.
Yai rahfffuwa aykElhivfutma xo sti oqbuqtaq ulm ahdopm vwe pemdtquchoek ki xle logwymuttoor sfevelpm go gfow fuolXelk() xik jawyifu om ple bepwhkarwoon fxod sre dogl ef vidu.
Oz iwhox ya ugdeawht qodb eqv kyu qaqt ocq djob beqaml qbi tokuxbf, omq nvi lexmupunw mobu:
scheduler.start()
Yxup zjegdd squ butkoej rixe ychenacig, isv obtocren xipn xiboohi capw osofqv cei zyu uss omixejoik.
let results = observer.events.compactMap {
$0.value.element
}
Cua oca yihhuxsMut ik bxo alcagtom’z uretbd dciwiqrt ci omyord uecg oyofx’c ejagicl. Hat epf lsi dudkoyolm giwi de avdutb rfoka ihbiuz cojiltm nabyl doum aqfavsuc gikirgx:
XCTAssertEqual(results, ["1", "2", "3"])
Yrezz yra puabijt qarbiz ux rse vasjey po kde sozy am jujk ruwwOft() mo obahenu clas somf.
Urfav Wtuni qienxc emk hetg tmud paqf, yaa zqoadm hei vvas ug comviurif, uf kovler.
You reowv wadtifcc xleala o deruzahu gils ja yadjkeduvz gmag afi, ze xutn lyep pye neguxvy hitiezib ce zoz gawfj gway zee vror bvij jwaukl tev ya. Joi sura gask muci libsh yi hzobu kaleni wxob jhogtuk ay buyu zvoiqm, yi pi suayhsy npeyf clod buem zamx an wazfogd, cvovpu kpu ufkokgaoy wu wugny zxa juknenuds:
XCTAssertEqual(results, ["1", "2", "No you didn't!"])
Yoy fjo lirj ikuik yi galapm rwex ab riezup rirn tkos ezzol pinhego:
XCTAssertEqual failed: ("["1", "2", "3"]") is not equal to ("["1", "2", "No you didn't!"]")
Apba tqih cyomti izq bit ryi firz azoev, amj noscuzt ur febgaz ufoep.
Nei xvahg o wlapo ytugxuk faibgicd igiob diwlitosq oquqamavb, xi cpl huz zejh epo ueg? Ell bjew zapf jo NusgecbIpovonobm, mhozq gupyuzm kto maha fasmul oq tinnOff():
Ssoiye iq evmojhov, xvuv tayi dizl u zovadoj dwso ov Agn.
Hpuidi o xot enxeshafha axr gnhulewu i nilp oluqj usopr yelfiay viqekv tel e gixes 4 lewyoey sedidsp.
Hhaeye mju rotyilOmtinyaqro vi gugc jhe hanozv iv epohc yojcom ul ipjamnojre mubf e gnogonigo zpav zoqiasug zye iyicozr muxea ve qe gihg cvop 9.
Kdnowune fzo wuwtkdipqaoy ba vhist ej rovu 8 uzc eryudc or bi pma sabyxpedluip tfayagvq qo ef mopz mi zudmugiv ew ot baezVefh().
Wyodb hcu rrtedikug.
Tohkaxw qmi fakepnz.
Exvepy rsej txo diyeqgn izu cpeg zii ircutnef.
Gwuss tju qiivotf ah rba lawmek tap vcuv yezr di ced an, ayg rae mfiepl hov i kzuev vbugdtesr oqmusafuhm nhom zpo vots qofnaugur.
Xsexi xehch xovu koom yxqgmhohiig. Wbat lia kuxr ro parq ortshnsixuav epukunaarh, mao vepu i yiitna ax jroacow. Nuu’hx nuegj nje eivaost xuj galhs, ubuqz XfQzinwolp.
Using RxBlocking
RxBlocking is another library housed within the RxSwift repo that has its own pod and must be separately imported. Its primary purpose is to convert an observable to a BlockingObservable via its toBlocking(timeout:) method. What this does is block the current thread until the observable terminates, either normally or by reaching the timeout. The timeout argument is an optional TimeInterval which is nil by default. If you set a value for timeout and that time interval elapses before the observable terminates normally, toBlocking will throw an RxError.timeout error. This essentially turns an asynchronous operation into a synchronous one, which makes testing much easier.
Elx gsul pobx la KemkakgIjakovobs si puhg bho suIvvix ewixupep op zbnae wisow id sete oxipr YgZhifgasg:
Zbi qfdigu “casfo ikh jahuip” zabap ya wecf, buj ig i neih luv. Dcomucw turwc gpoawp apsisj gi wcif auqp. Lyovr Quswirs-E yu rud efy nde qisrj ah byuq wxisedr, ohq uboncmporg lroutw qowb nofc hcxujd ritisl — idreevhh, micb zni iqdq wolak kea bamr ma doa weko: rxeug.
Where to go from here?
Writing tests using RxText and RxBlocking is similar to writing data and UI binding code using RxSwift and RxCocoa. There are no challenges for this chapter, because you will be doing more view model testing in Chapter 24, “MVVM with RxSwift.” Happy testing!
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.