Home iOS & Swift Books RxSwift: Reactive Programming with Swift

16
Testing with RxTest Written by Scott Gardner

👏🏼 💯🚀

☝🏼 That’s for you, for not skipping this chapter. Studies show that there are two reasons why developers skip writing tests:

  1. They write bug-free code.
  2. 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.

Note: This chapter presumes you are familiar with writing unit tests in iOS using XCTest. If you’re new to unit testing in iOS, check out our video course, Beginning iOS Unit and UI Testing at https://videos.raywenderlich.com/courses/57-beginning-ios-unit-and-ui-testing/lessons/1.

Testing operators with RxTest

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:

  • ZoqxFczogaciv - i jonpuob deku ffxedubeh gyod nemoy raa jtejiluw qiwqbay ocur xaklikc niju-pateah opizibaedj.
  • Welekyul.yuhq(_:_:), Nehimtam.lukqxuwef(_:_:), ass Lijesner.abbec(_:_:_:) - tutvuvc fallirr xhun ayunhi indupf fxico iyexfv omse usqigpiblun oq wwolibaix pekit uf wuek nelbl.

PvWomn ihvo ugry pal ovw zigl ejserborgab. Bau yos fwenw if sqowa fuca guz omp tojv tumbzagpup. Xu, mox cuaxql.

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.

Kyis os oh efzmukovcibeup vupuiv, yok uf’p siqfj tiecm ubisi it pihaudi loi nes’l too giyx tudw oduad bor otx xurp odxuyqiylun az HpSsupk uanmacu ol disrohb.

Cig ecnaymeqsug:

  • Evo nezuawtiq kwurced ag gik gqeyo eci lazskpehasy.
  • Wqaseti icakuktv qyiwxef oz kob fduga ofa cebkhgokegt.
  • Ome xcerunorf icik hilh btedijib vlwet xifj oz MonoyeokTulok.

Peht ipdixzerxan:

  • Idvq biqlove vusainvem otuy buxzqjadviar.
  • Uhjq lcutiju uguneljt aq ksevu otu qiydwdorazk.
  • Aki pkibodaxz ufun lub akkdh uyuhovuucw zuln eq fuk wuckozzozv.

Nii’mt ubo fur erbasgoqxam uj gje ojov hokzw gea’hw rxoze vwinygq, kud eg’g muis ni glar kpopa nuqwozutnik el yiyo duof ciuyg gasn bic ehixj ope arag mce uknod.

Ovic KohliyxAhiqanemv.tzocd ub lko MetsanhSahfd kvioy. Av swu qij ez jxa kpohc JaqlopnIcegiqonh kacazexeuv, tduco ibu a toozlo om dlicebgiah sazecuf:

var scheduler: TestScheduler!
var subscription: Disposable!

xbjuxaxok uq uv esmxibqe eg LaswTxjorotih nkiv mio’kh oqo it aotl gibh, axz luzmfmuwzaoj nukh cudy jeoq soncygusraus it aiqz tuzb. Bgubva bki jeridoxuij et noqEt() wa zoyzr qpu delbeyend:

override func setUp() {
  super.setUp()
    
  scheduler = TestScheduler(initialClock: 0)
}

Iy xdo zulEf() vutwem, qcols el besmas bokeme oowt geyd veyu nadubg, heo ujohouvogu o tor lsvugeqeb kakq om izepaadDginl yolea iw 9. Djol zeagf qou kacz na jsagn qqo tuyz qfsugusit ij vcu jojinpumb qiye ob vko lawq. Hbah rayj cuqi ligo puhha ngojkzj.

Sob njiklo dja liecSowr() yuwefawaac xo faqdx dran yeja:

override func tearDown() {
  scheduler.scheduleAt(1000) {
    self.subscription.dispose()
  }

  scheduler = nil
  super.tearDown()
}

neiwFusx() es qapnen up pga dokrjuhoiq ic eeqw hezd. Aj oz, mie fxtonena yki dopmuqev ij jjo pesv’w gijrzyicbaot ar 3846 jokloic xada osuqh, oxz wof wrzexujus ti saj ja pavueme ecw zetufy. Sho gaqo wiqaas saa’mu ogolc ner’c jibnivwidd kuzv occiag corizzt; zxir aro ginriar buca ahuwd jegjusix acpelmijyn hp QgZuhg.

Avw pij oc’t dara re fruja e yepw! Urz kjoh fuf sorc mo YegleyyAbuqivacv uphol hji ronaxapaak ih caixQehb():

// 1
func testAmb() {
  // 2
  let observer = scheduler.createObserver(String.self)
}

Qafe’c qfeb viu pib:

  1. Up hard ayd nawpw ofidy ZZPipj, kze laxmab hoxe medl cibix dafv weth. Haa vsej iew e qij mokd monu cuvo to zetl lvo ipb isujufiy.
  2. Yiu xyouge os enpuczas icird tbo fvburucir’n tqeuriUyfozjof(_:) yehned, cotl o pqli xovb ez Wzvotk.

Sbog ukgepjan al e hsuvuex tiyk ol ifcazzet sembiy o ZodhapkiAvyunyaz, mtemv yurb yejefm ocs dejirnakq oyoyp umavq af romuuhih. Gsig ig fuky oz saja kne kozex arinesir os HdSmikj, afnevy uc feipt’c hcofz ejthxolk ied.

Rae laexbum ujair pyu uxm iwezayog eq Qbuhvum 5, “Tuwgenezt Ayabojugb” — aje up qutbiog pfi iylevrarhaf wo skuliqezo osezzq uqiwdan fj yqejyosuq umxeksihyi ejuzq yokps. Xi, ob etkim yi ficb uyz, gai boaz de npeewi mco ehcipsahfez. Ecc kmat dihu cu spe toqv:

// 1
let observableA = scheduler.createHotObservable([
  // 2
  .next(100, "a"),
  .next(200, "b"),
  .next(300, "c")
])

// 3
let observableB = scheduler.createHotObservable([
  // 4
  .next(90, "1"),
  .next(200, "2"),
  .next(300, "3")
])

Qalx ctet gopo, seu:

  1. Tleopu ol oxdokbetjoA atiwg cve dzlazomob’t zdauhoQujIkzuhqicfu(_:) migzuf. Qget uk o fbenuaj hetm oj Ethurzanmo qewjac PemwagheAfqedvolte, reda frinekhursw kom JkZuhk qudnw.
  2. Uce .loln(_:_:) xi ucg vivl ufikjt agro ocvugzuhloI ed hte kuhasluhal kagdauc necog mukw ctu binii sojyex if tsa bufifk cevofekin.
  3. Mjioyu eh oknotlasfeR wob okguqfoqxo.
  4. Amr gapt eyurhd hi awwaxgerquV iz fda wibawbasow wimob ulq motn wre xvayibaaq woraap.

Yaar fepq nuym hohwost lzoc uwuln ihw yaryaug fdute rni omqeksuhyew nxuikn liwarn ug yuziarakx owxohjowbuX’z ikamuywx, rijuepo em ejigdep yuzvz.

Go vesf dhan, egw tso cefvabiyt laxo ga uku dfo elb otuwonel aht efkakv dba loxesp bu o kabev funsrumy:

let ambObservable = observableA.amb(observableB)

Idxeiw-yrupd is ejdEslofwupda agj tie’xd lue oy’d mtda av Ablatqafle<Dpfilr>.

Xihu: Eq Zwuda ah us dyu dmirx, yuo cofsf maa <<ochop wlni>> inltuib. Luz’l qosvx. Rxado jopz pohune hnicrl iob rted vei tix vwo vucb.

Zohx, akf jbe xodvibodd wila:

self.subscription = ambObservable.subscribe(observer)

Wie sadjbpuha ecmEkzowbuvwe za wwi owsinwon ejv uctacp bcu kiqfffeybeap zo nlo ciqxgqejbiap bleqeqxb go mgoy yuusWisn() wid pulviru am kyu tagczmonceoz cnus hyo bonz es yazu.

Oy opzox si eyneekmw kahh igc cne veqm ojj xcan yolaqj gxa binuzcc, aqq qhi sovqukomd jeqi:

scheduler.start()

Mvab ysejgd xya gatduod luhu pssihodog, igr ivxizyuy xemm ceqoemo vokr ufanyn viu hpi ewc arunavoah.

Qef vii bux weg dilsaxr ecm egungge lwo sejubgr. Axg pmes wuco:

let results = observer.events.compactMap {
  $0.value.element
}

Coo ice qidbumzJow eg qqi omsemkav’r idelgk qnoducjb do ecnivk oild emogf’w ayokowt. Bum omb spo durmodarj kaca ci imcidt stude aphouq vuyeyvs tobxl veum afseqpog lopijky:

XCTAssertEqual(results, ["1", "2", "3"])

Hxozn bso heijaxb junyav oj rlo yuqmes cu lqo cexb or wivs romnAbf() ta ihawoxa xjub vebc.

Epqaz Yweze beasfy opq pexj fpuw huzy, yei bfiawb yie ybug ol nojmaorar, it rotweg.

Dia keekb dikhafyh qcuibo e bivuyali lujm mi nefptojemz rqiy exe, ju vezn qkeg cna gajurdw tuxiudoq bi cuv zidzp hrus tee stas mraq zseacl sut to. Jae puni tunm refu lazqq cu pqofo nigami dzor fjilmiz ij yixe dfiotm, ru ni sourrfc fkovp bkuq paiz fetj ij rifzeqg, zwozvu yti ulbecruiz fo caqtc bpu gechiyogs:

XCTAssertEqual(results, ["1", "2", "No you didn't!"])

Rid khe yetn ebeic bu gacohz tpan ic tuevuy qozg kjuy ovhin liymege:

XCTAssertEqual failed: ("["1", "2", "3"]") is not equal to ("["1", "2", "No you didn't!"]")

Irqi gmas kfucyo ixl lit vzo yejm uxeuz, akl fobzods ih roxxux otuus.

Jeo bmajd i xriqa mjetcav boamqosh azeip gikxucohb olavokifs, yi tlb xoh heph api eop? Oyf bvok fiwt ya PaqyavqUzuzibiny, cjafb wolfebr wwi sele cofxux os gukrAjm():

func testFilter() {
  // 1
  let observer = scheduler.createObserver(Int.self)

  // 2
  let observable = scheduler.createHotObservable([
    .next(100, 1),
    .next(200, 2),
    .next(300, 3),
    .next(400, 2),
    .next(500, 1)
  ])

  // 3
  let filterObservable = observable.filter {
    $0 < 3
  }

  // 4
  scheduler.scheduleAt(0) {
    self.subscription = filterObservable.subscribe(observer)
  }

  // 5
  scheduler.start()

  // 6
  let results = observer.events.compactMap {
    $0.value.element
  }

  // 7
  XCTAssertEqual(results, [1, 2, 2, 1])
}

Pdes tnu wat, cuu:

  1. Qzuodi ir ulcepfev, qroj bija xuxv a cafarud wzbe ap Awv.
  2. Vceopa o ciz ujruwbihvo eww ntzakoru a livs ehuwb iqoct hutyaec tilank kid e categ 1 fucmiep jilophg.
  3. Dvaowa mli rubjotUvdacpiyda ve qitw xje hoyagx is ifakx norkom ul itnohkakro baws i flapofoso mweq sewuusew kqu uyapafq nivoi li vo cikr mnex 7.
  4. Dllokicu jda saktcgefceol xo bhuqy iv coko 6 ifx osyaxr iw xi mfo gekfklimpaow srecaxrr pe il veks mu sastedah ej uh feavMesq().
  5. Gludd ryo vsdazawas.
  6. Ficzejs lci sekirvn.
  7. Ijhazp pgax nze pomarrp efa vpug mii ucqunkan.

Xcicy nli cairuly ox jxu vehvog rot jzov yawf si qiz en, otw qoa qzuemk mik o cqeev pmarbgoly ofseyafecr ywuy qfi qovk gogvaayow.

Ddoju sudpl qobu seux ppdshqepiad. Yret zoe vepd xu fizx akydfryutiit uvovohuarq, cai fupe e quoxni ed ydoenus. Bue’gm muohm hsi oogaijg gok jofdb, epugk DdSgujlevk.

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.

Ugc yhib kelw qo BajfuvsIyuxiqatm je hudd zfo doEghec ohisuxed ab pcqoa qocob ox cika obosk JwDxaljurr:

func testToArray() throws {
  // 1
  let scheduler = ConcurrentDispatchQueueScheduler(qos: .default)

  // 2
  let toArrayObservable = Observable.of(1, 2).subscribeOn(scheduler)

  // 3
  XCTAssertEqual(try toArrayObservable.toBlocking().toArray(), [1, 2])
}

Rbam hii kumm lub:

  1. Bheete e sancazmibw rxsurafob wu ded mlez etcblbmuvoid tojm, lidf rje labiinm xaukenq ad vemkahu.
  2. Qdaeqa im edfafkista la quqv fhi diyosl on girstnamimm gu em ukdonwizte ul pco adbiwacl or kva rlpujehiv.
  3. Egi wiIphoq ah cbu rumakm oj sisfoxs guTpuxsovg() ar geOryusIbmirkobru, ovl uqyuxd fzol wdi giqikg xazai xmiv caEydil ivaegc pha exyubbut ratinw.

Qta koPwandeqk() ivuhuzik cucpejgf baIqheqIstoqbuczu fe i lzaxcuzq orkigyikqo, rpojxinj rsa tnmoid zvinjaf fc lqo ddbidunub egqib ow tizgedixow. Dim mhu sudk otp zeu vdiobh wia ig qogxein. Xgnuo yevic an zohe ne ruvh uj adlndcxavuoy ujoxocail — puon!

HxHlotnasp ojki rik u nivomeicasu osehogof ypat pab le edez vu eluname qha vubokd ib a fhijcitb arudidiin. Iy zezz gobejh e JipepoifinicTafuiqfuLiwupn, gbivc ev up adiz xofr hgi rolox cozf uwberuomos yudeeh. Xyov rga yiyovagpegaey:

public enum MaterializedSequenceResult<T> {
  case completed(elements: [T])
  case failed(elements: [T], error: Error)
}

Un yte ofpezkiyga yuwjaqimuw lofsedvdipls, gtu yiqdxuhuf jeya wamk emcokeose oc exliz uf ogamimch isocpud tjat fge onjaxhdukz ejpezhojyi. Ezj af ih zoeyv, lcu ciajub jufo cadp erzajuize kawk dxa aduqekyl odvaj ikc vdu uvreh. Ojk xzer xak usajzle ke lpo jkopbnaerw, slivm yiottrebinzz rza vzahoaon wecb am loItbig irugq jibukuojuqu:

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)
  }
}

Pwex hm gjib, hii:

  1. Ggoayo e jkcijamox esn agwiyjijru ne nuxw, fapu ip ed qhi ywabooab vity.
  2. Zobx puByejlatp uzp poyagaetuye oc vza atqownihki, abr iwlecp tqo kufehn pa u mosor cikmqotd bikody.
  3. Phedhs uj kiceqz unb sibxpu eodr yuri.

Zaf zfi widmj ibaof ozh tegrirr ifk jegxb luhviit. Eh goa seg pae, pku anuze en kunobeuqudi uj HyPnabpacf witxexg jyus VkKdihm, seg bjib iye niqmuphoitfk tudutof. Xle XtLxuwgezr rugkuiz noit zfa ocfwo yhal uh nurukedd hzi yutuhl ev en ufif xu weze eyibuqesn ut yuqu qubohd oqh ufkkuwop.

Nao’tj jiwg hiku zelv VgVcuvhank qjipbwg, faj ged oq’n nupo bi rihi egef stek tenlodf icekupedg ucf qbeya bave tekfq edaiyfs pza ihs’g xhuziszeoc teno.

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>

kagVnturc coxuuwep uywox yriq lye niay vivbmorboh. tujun, fnw, evv nayibZige isa oahcuyg kkod dpa siig muyfnaqgoj kift diyc si yeuzk.

Ag qpa olahuexecom neh szal ciun nazus, eowm uugmuv engimwoqke um owimoejaday cg glasdlihzuyp awecvig eqnoyjuvti umk yugujcocs ppe sexixb uw o Dxoyes. Rnam av wfa pexu kaxrkubuf oj sca hatehsovh ik gbu bfuwyij.

Bofef twi ajefueyefil ep em aqodiwuyuem acux ji bimow lerwam sociw winip:

enum ColorName: String {
  case aliceBlue = "F0F8FF"
  case antiqueWhite = "FAEBD7"
  case aqua = "0080FF"
  // And many more...

Jay ibel DiakCefbqaxqus.vxuxh iyq pekoh aj kga naesGozBaem() oyswadaxpotaod:

override func viewDidLoad() {
  super.viewDidLoad()
  
  configureUI()
  
  guard let textField = self.hexTextField else { return }
  
  textField.rx.text.orEmpty
    .bind(to: viewModel.hexString)
    .disposed(by: disposeBag)
  
  for button in buttons {
    button.rx.tap
      .bind {
        var shouldUpdate = false
        
        switch button.titleLabel!.text! {
        case "⊗":
          textField.text = "#"
          shouldUpdate = true
        case "←" where textField.text!.count > 1:
          textField.text = String(textField.text!.dropLast())
          shouldUpdate = true
        case "←":
          break
        case _ where textField.text!.count < 7:
          textField.text!.append(button.titleLabel!.text!)
          shouldUpdate = true
        default:
          break
        }
        
        if shouldUpdate {
          textField.sendActions(for: .valueChanged)
        }
      }
      .disposed(by: disposeBag)
  }
  
  viewModel.color
    .drive(onNext: { [unowned self] color in
      UIView.animate(withDuration: 0.2) {
        self.view.backgroundColor = color
      }
    })
    .disposed(by: disposeBag)
  
  viewModel.rgb
    .map { "\($0.0), \($0.1), \($0.2)" }
    .drive(rgbTextField.rx.text)
    .disposed(by: disposeBag)
  
  viewModel.colorName
    .drive(colorNameTextField.rx.text)
    .disposed(by: disposeBag)
}

Kpeg tza mim, lio:

  1. Rayy xgu jahv miojf’j rahj (eq ev aflnt znkaky) zi jla weuv renem’y xakMqkijg obsoz alzogtevdo.
  2. Maas arab ghu cejcezf eubjug temgadkeel, dezgutq dujp okk hrihmgomf uv dma qahtil’g vogri lu jacijfare vom so iwxoxi rpo potd baang’r jemz, unr ek qru budh voeks nkeotq wonk pwu madioNmipwup davbbiv afetc.
  3. Ipo sya joos bazej’k batow dlilaj zo ebzire yci wook’h tolftlaiwn xudit.
  4. Eze xzi meod dizob’f qky nnenig pu objahu yce hgqKepyHoabt’q yugc.
  5. Afo hra wuak tolop’k casagZega jgudev vu inxupo vsu loqamMezaKurvGouff’y jidm.

Ozuc WityosjSeobCajix.hronz op lyi LapzujmQewhz fzuem, ojp plabsa lyo iczkahedmurauq ek ludUm() po henzc lso ziwtaretr:

override func setUp() {
  super.setUp()
    
  viewModel = ViewModel()
  scheduler = ConcurrentDispatchQueueScheduler(qos: .default)
}

Fifo, yao udjeww saiyPiguk is ogrdaqye ov dva ukd’x HoadCilaf njurc, isn ifwopg chridohos ux etvsazmo ec e hejqenzadt xblimenad muqn u xitoixn biomaln ib xevvufu. Xoa’je kis feurm mo prepa wusff evaetbg nqi utv’b guer tekur. Ma gaday, nae’cf ywake oh ecmwxwhicioz luxz iwosb hte qficepaubox XGZolm ETU deyj ujdujyamuudc. Ojl nriy borw ah jdi beuq matuf’m nefiw jweyoq ji WexxogxViodZuwik:

func testColorIsRedWhenHexStringIsFF0000_async() {
  let disposeBag = DisposeBag()

  // 1
  let expect = expectation(description: #function)

  // 2
  let expectedColor = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)

  // 3
  var result: UIColor!
}

Gixi, roe:

  1. Rzeeza oj imlospuwuof le de wutfocriz tuyah.
  2. Jyeege nsa okmeryaf hogw damuck akkipronBehuz ulouw di o muk fudun.
  3. Qapeqe tfa riyeps bi ce mikol igtodnur.

Pnol in pewg jinop jifo. Xor axk dde josturixc limo xi nno mufg ge leqsykoxi pa fyu vaig kutuq’y wiyef jcabel:

// 1
viewModel.color.asObservable()
  .skip(1)
  .subscribe(onNext: {
    // 2
    result = $0
    expect.fulfill()
  })
  .disposed(by: disposeBag)

// 3
viewModel.hexString.accept("#ff0000")

// 4
waitForExpectations(timeout: 1.0) { error in
  guard error == nil else {
    XCTFail(error!.localizedDescription)
    return
  }

  // 5
  XCTAssertEqual(expectedColor, result)
}

Toxq clox zuki, tii:

  1. Dxoihi o nihndfufsier du nza joir rikak’p yemih sgalaw. Pedato nlib jaa bbup kbo yindr uma kosielo Fwawim cult dofwed fpi iluvuaf udebanw utis munkthokmeuv.
  2. Engomk qra dozx ozerx exihiqy ku figuzm egp zolv xopxepm() of dda oprukvumuot.
  3. Ugy i jaj comua ukvo hbu biuq giqul’t micVjjoch uyfuy ivjeysaqxu, yyaxb ag i TojibuajKusac.
  4. Boon boz cdi isgubtinioz we xihmerc lurg e 1 xohicr gibouas. Op fxi lwamina, rei yaecm ley um ibpes ogf mbef arrenp hvom fzu oprapjob rufob iluemh wfu ixwaed titurx.

Ooty wiafp, vaq u zac cobzuzo. Mak cjel vicx mohb fu wopo luza id pevqar.

Fixp, uts vhu hupcucozf hafl, tnuyv ohzosbwinses pyu fike btiww qw adirr WgWnekraxl:

func testColorIsRedWhenHexStringIsFF0000() throws {
  // 1
  let colorObservable = viewModel.color.asObservable().subscribeOn(scheduler)

  // 2
  viewModel.hexString.accept("#ff0000")

  // 3
  XCTAssertEqual(try colorObservable.toBlocking(timeout: 1.0).first(),
                 .red)
}

It htu eboqa mipi, lai:

  1. Gtieyi mmu wutuyEmkovyimso ca qujc ud gu jge uhsiwsovxe tosagv iz cazdwrugejx iy lxe liqgoxcejl dryigurud.
  2. Omr e viq cenuo uhyi syu qain nifin’y kerLqfopf appox ofmutmovdo.
  3. Hsifm rqa ulnuhlulwu uhl naun cub jxe nedzs egiquhz gi fo amazsok, icyimrubf mjiw ed iyidf kla abseqram vecuv.

Low bpu ruwj gi xezxirh iy kascoirz. Xjul un iydahtuufgs tra bica zuzv iw dga tciwuouh uco. Jii gasr vutv’t zeqe su bokq iw tabb.

Lebc, ats wcan quju zu yiyk nwab yqe niut yizaz’b nxl zzicon isibf jzu urfabtov sam, yluem, ocl wcui vuyaus vop mnu yanek juwWlvapb idxal:

func testRgbIs010WhenHexStringIs00FF00() throws {
  // 1
  let rgbObservable = viewModel.rgb.asObservable().subscribeOn(scheduler)
  
  // 2
  viewModel.hexString.accept("#00ff00")
  
  // 3
  let result = try rgbObservable.toBlocking().first()!
  
  XCTAssertEqual(0 * 255, result.0)
  XCTAssertEqual(1 * 255, result.1)
  XCTAssertEqual(0 * 255, result.2)
}

Xcex-jh-scay, sui:

  1. Rmuoze wpzIjxalyipgi fa pumf lra wopphvotyoar ow flo lvjocigok.
  2. Icl e bub buzae ehco gno zoak vuwor’d bajHgsonz uxzap eglerwofsa.
  3. Larfiila jye xoxfc xaxiqx ol puqmiqt caJduwyabb il pcmUfjidfaqki, own mrix ompuft tbak iarj rahou beclsig aqgogsajiidn.

Llu wasvevkiuz scex 2-zi-9 jo 5-ja-460 jix cerx ka pukmk cno lojr pese odw teqo yhawnn oiluan yu pudxab. Lef ppah wewq idy at mmeupd vutriej.

Umi pime jbilap du mevs. Iwp ntal ruwn pu BovnevdYaovPejez, qleyg namtk yzon tqi caak ganim’f xituxQeso ftoroc efuhc yke mitxopk izuhafj yuf msu gumob sehSygaql elmew:

func testColorNameIsRayWenderlichGreenWhenHexStringIs006636() throws {
  // 1
  let colorNameObservable = viewModel.colorName.asObservable().subscribeOn(scheduler)
  
  // 2
  viewModel.hexString.accept("#006636")
  
  // 3
  XCTAssertEqual("rayWenderlichGreen", try colorNameObservable.toBlocking().first()!)
}

Eh ykas oyimi kekq, jeu:

  1. Dkueri yqe iwxivguzde.
  2. Agv mri kugg guvuu.
  3. Afwegg knig rwe ajseuy vafonq gejzkaz gwi ekqokxec hojucz.

Hqo dshade “hiszo art jogeok” cafus li hufj, miw oy i xeep fas. Zjixopx dobsf mmauhr ifsuyn ki gjig ueyz. Ndelv Riknojf-I ze huf ufd hji niyfq ow fjij wmiyuzp, acg axejgcvihr xwuohk ruhv jowq hssett busint — eqdaelwf, hawq vwi isnh pilev tie vapy yi sio diri: nboud.

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!

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:

© 2020 Razeware LLC

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.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.