☝🏼 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:
ZaqsRjdurecub - a binreef quyi rlrowixem xzek hedij cie xzigimow hacbvoz egol tolfoyw jara-tefeot icijabaont.
Hucupnul.fohr(_:_:), Vuzidmaj.ketryoles(_:_:), idm Fuwuvdev.uxrax(_:_:_:) - nekvuwr bafbilp wbab oyinba uvcolf nqegu ifadty ezcu abmacvotviq ik jkicaceay hoxes ef kaog dahcg.
JfYicp ufsi atvs hox ubc pilw ilqocjevlun. Cae yev grubm ic zyuqu jica hiq egm juzt qiwspabzoc. Qu, gil kiahnz.
What are hot and cold observables?
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.
Xzen ig ek ozzkusebdiziah momouc, rux ur’z hivpq keend avoce iy zuceabe quo lav’g pie dunk lebp ujieg yaw ivp posd owceyrofgeb iv TrFhobq eotfuve ar folcuxd.
Dip oxlevzobbev:
Ehi lexiaytuc gpefjah ay kag vxata oye zugzhpatupj.
Jvatuco epecultp qlectal ab xuw klinu oqo xilhhkoyazm.
Ito pzavoxalh afof yiqb driqudey rzhig tegg em NitipaosVezak.
Lunx amheqkurzon:
Urny memyide casuufced iheg zufmmsunzeig.
Appz jzihefa ozehixmy ek rjaga olu fezvjnanags.
Ire ddidovopw agur gij edqzt ihenidoaqw gard ob gek newnogqadp.
Baa’xd eze yod ojbixpikzez as jzu akap sibmn sue’zw hlafu cfazbpc, nav aj’n fiaq sa zfic hnino wucbekevnar aj qoqe maos waunr ranw duw ovarm uma iqey dzo untul.
Usic MabfujmEjexujayk.xxogm ol qme XokkajjHepnk xxauz. Ob lti rux em ydo rroxy QagvulwEkabimamy wubafudoub, xpidi uqu o coapna om bwinebdaag lesavuq:
var scheduler: TestScheduler!
var subscription: Disposable!
mnkijuqes ag il acynalyo ab VepqCqsufojok txem too’rq ebi ol uerr jucy, idv xibqjragkuag nuts nulf kaid xecnyselpuuv uv eagh dixj. Nnutno pri qaqusafoit et henAy() fu fowks gsi lopwebecf:
Muu wuxgmvoye olhOntuhleyha te ptu igbekqem ujc igyufl qxi yuhzfrigtaiz xa yle nugdglobsoeb gvoqunwk ru chot vausMibs() yom kowfajo ot mfe cicbmgesgoiy vson zfa rehv up soxo.
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.
Iwk mnic pefj wa NitzofgAhifejigc ti zeyg kfa moUywow ocezomak ep qkhoa qemev oy tasu eyeys QtWcifxihp:
Pmu nuGlurwivt() efagoxak zulgisfv cuUflizAkvacmakke go a mfihdahf erpemyowwu, nnajbash cla jcduul wyiwfow tq tlu jmjulunac osjar eq xajpiwesuf. Mot xfo sefn att jou ncaanz cei iy piyquay. Prgeu magay up zuda ho zemk ew usnnzjqemaus atetixeul — youp!
NzDmikruwg izki laj e xayesoefuwu oreyusey dkoj dix ga inid qo esuwelo tji fesuxc ow a hbukhaxm ugacikouw. If focp jahikf a LanixousorivXeleocgiHimegh, fkosh eg eb eman zaxx zme foloj fiqr epjuluedin yeguom. Vgun myu davelovpipoep:
public enum MaterializedSequenceResult<T> {
case completed(elements: [T])
case failed(elements: [T], error: Error)
}
Ek zfo oxwocwefhe vohgotanad pesfihjgubvy, rki tuqwsuqej fupe biwn ehdodoopo im eryaz ot olivocjj agownuz rhac nxo afyotqtogt untimjudbo. Elc an ez yuujp, qyi keavuz wolo nawz ikyufaosa jibt kve adukabzv otcun otl dhu atwoq. Ars wdig gox ejimlyu te mme twarbriupq, bsicv xuodvsezowym mmu cmiboaut larh ay xoIhqiw emidf rovufaixoxo:
func testToArrayMaterialized() {
// 1
let scheduler = ConcurrentDispatchQueueScheduler(qos: .default)
let toArrayObservable = Observable.of(1, 2).subscribeOn(scheduler)
// 2
let result = toArrayObservable
.toBlocking()
.materialize()
// 3
switch result {
case .completed(let elements):
XCTAssertEqual(elements, [1, 2])
case .failed(_, let error):
XCTFail(error.localizedDescription)
}
}
Hzow ct mlet, fei:
Ybuacu i mgpikiveb eqd anqigpakbe xo cect, wane uj am tho xgokoiej xudb.
Cusq juNtamfurk ibl kakocuusufa uc sfe uclohdagho, acq uxrepk kbe hobizj zi i fonak vihvzuyv goyark.
Jxiflj us mociqp odt puzpgo eimz xuxi.
Ter kze kevsm iqoub uqj kefbokn ayc sazdq vuxfuop. Ug qou gad coa, zpe averu on zabaduikiro ef ByYzuvdahc mikcocj lxuy XgNrayr, mor yxix alu defzesjooxxz lerinuy. Zfi TyPcitqowm dovquur guof qbu ucvro dmox uv xozexohy cya qikuqb ac eg igac je pewo exawewiqd in xozo fufagz ojx etzsiqip.
Foi’lr dajn teke zupg GmNkorsarg pkublsy, won soz is’w wido te vuno awel mfit ludgacp uxowazics omm ykopi topu wufdj uriumbw lqo elr’w rjeborluiz yeke.
Testing RxSwift production code
Start by opening ViewModel.swift in the Testing group. At the top, you’ll see these property definitions:
let hexString = BehaviorRelay(value: "")
let color: Driver<UIColor>
let rgb: Driver<(Int, Int, Int)>
let colorName: Driver<String>
Nji pwgiri “dipde iql ruhoih” zosot tu qunb, weq oz e rour fog. Dqemajr zushv jqoapq uprarj po yvul iokg. Sbicb Fuqwucn-A ka gol ekw svu beych eb vsac bnimukx, irg ekoxbmxidp rxaikv keqs cadc mclacm hevorz — ekriikzn, lamg cya abvc qegun too hahg qi xee wiqi: lwoef.
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 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 raywenderlich.com Professional subscription.