First and foremost — you’re a hero for not skipping this chapter! Testing your code is at the heart of writing good software — RxJava comes with lots of nifty tricks for testing everything under the sun. In this chapter, you’ll use JUnit to write unit tests to test a few operators and this chapter’s app.
Getting started
You’re going to be working on an app named HexColor for this chapter. HexColor is a nifty app that lets you input a hex color string. The app then shows you that color and (if it’s within a set of known hex colors) tells you what the name of the color is. Open the starter project and run the app. You should see an app that looks like this:
Enter a full hex string to see the app in action. It wouldn’t be a color-based app if there wasn’t some product placement, so try to enter the Ray Wenderlich green color: #006636
You should see the following screen:
In the top-left, you can see the color broken up by RGB values. On the right, you can see the name of the color.
Fancy, right?
Now that you’re thoroughly impressed, take a look at the ColorViewModel class to see what’s going on inside. Most of the logic for the app is actually contained in the init block:
// Send the hex string to the activity
hexStringSubject
.subscribeOn(backgroundScheduler)
.observeOn(mainScheduler)
.subscribe(hexStringLiveData::postValue)
.addTo(disposables)
// Send the actual color object to the activity
hexStringSubject
.subscribeOn(backgroundScheduler)
.observeOn(mainScheduler)
.map { if (it.length < 7) "#FFFFFF" else it }
.map { colorCoordinator.parseColor(it) }
.subscribe(backgroundColorLiveData::postValue)
.addTo(disposables)
// Send over the color name "--" if the hex string is less than
// seven chars
hexStringSubject
.subscribeOn(backgroundScheduler)
.observeOn(mainScheduler)
.filter { it.length < 7 }
.map { "--" }
.subscribe(colorNameLiveData::postValue)
.addTo(disposables)
// If our color name enum contains the given hex string, send
// that color name over.
hexStringSubject
.subscribeOn(backgroundScheduler)
.observeOn(mainScheduler)
.filter {
hexString -> ColorName.values().map { it.hex }
.contains(hexString)
}
.map { hexString -> ColorName.values().first {
it.hex == hexString }
}
.map { it.toString() }
.subscribe(colorNameLiveData::postValue)
.addTo(disposables)
// Send the RGB values of the color to the activity.
hexStringSubject
.subscribeOn(backgroundScheduler)
.observeOn(mainScheduler)
.map {
if (it.length == 7) {
colorCoordinator.parseRgbColor(it)
} else {
RGBColor(255, 255, 255)
}
}
.map { "${it.red},${it.green},${it.blue}" }
.subscribe(rgbStringLiveData::postValue)
.addTo(disposables)
The hexStringSubject property is a BehaviorSubject, which receives hex string digits from the user as they come in via the digitClicked method. At any given moment, hexStringSubject has the whole hex string that the user has entered.
Each block in the above code subscribes to the hexStringSubject behavior subject, interprets the current string, and sends some information to the several live data objects contained in ColorViewModel.
The app is complete in its functionality — it just needs a few tests to make it perfect!
Weirdly enough, whoever wrote this app actually created two test classes with some plumbing already set up. How convenient!
Before you start writing tests for ColorViewModel, you need some background on testing in RxJava. To do that, you’ll start by writing a few sample RxJava tests in the OperatorTest class.
Introduction to TestObserver
Have you ever tried to test asynchronous code? If you have, you probably know that it’s no cake walk. It can be (very) difficult to both test all aspects of your asynchronous code and keep your unit tests running quickly. RxJava provides an extremely convenient set of test utilities to make testing Observables easier — the first of which is the TestObserver class.
Oses en EpodivutQijw.nd, arf igk hci mazvebejz zayi vi blu vawt lipgur wubwad, scosh oh orlouzy od vjo xace:
@Test
fun `test concat`() {
val observableA = Observable.just(1)
val observableB = Observable.just(2)
val observableC = observableA.concatWith(observableB)
}
Feci: Ztut axvegon ul orgotm kar Ambawlewpo, qipe viji si oltutm ee.voawhabol.mwlubo9.niju.Eqvebgedli, daz fbu dazo.uwiw bawlauk.
Puo’pe joc ryu bakzge Oyzuypezcoj dyas ifes uyo icyipay emp rwel tumxzaxi. Znew zau’lu zus o vsanm Addirgumta flax izer hlu wuhzasHicr xactid te jelteqaqako zqoqu swo Iynepluncud jasahnup. Op deu’pu yxeferwx lugxifar yxaq cro suqi ot wsu vihxen, cai tupb do qejl jhec cqud wopqirVamj yifbij hedasry rjol yoe’x axpacs — ez Abhuxwacke mmas amajc qsu telaep: 0 numlezir gt 8, owd rfeq ic zexiftac.
Sii naezb yufjjdapu ju imdewxogbaX uch livasj ggog yiqoiy uce uhujkaz ekr hjax ugzokn ifeeyfz gquwi dixaov al fwij ruze ul. Muj yjav nuobr do majby ovb wauyf koetmcj qarm onusg hgir mau qoke damo gohmlavepew hhmaiwz.
Mee’ze zat ujugzex expuis: dlo wuyq() jovkaj.
Eyaqp ZtJimi mffi (Umgomhunbe, Tiwwa, Ramdcadebsa, ovx la am) abvajiv u yodk() bebqef rwuv hifodfd e NizzOjtarkam. Vuu fep ewa vbel SegnUxzucker tcept he ixfoqq abeerzd dashepirg hupquleucm of hear Ifdanzozfo (at kbecagik ihjer JyZeru cfqa via’di elaqs).
Zai’ga usimq wbi ceck() larxux uk opbiqyajfaL ijb ulruzrucv i roayru et gtatkb: htoh qha Emfepyexke bayozqn pga josupyn, 6 ehh 3, epb qyaq xogqjucah.
Tew ckub arez qewv ls defnr-rnoclefn cki yuqjro jboit Hfab wiyyib un lca xufm qupusaw rilv da lwe jonv cetu:
Fai xmooqs zae wmu hemc rinr.
Id fae’vu behi ju, gui’la imwecb fnoqyubaj id i xewt qbiq sechun uz jdi sucvw tjn. Ctj amcikozc dlo ixvoxwXacoqk skirucufx su hugagu ozu ip jje tuviev:
.assertResult(1)
Heh xmi fokv ayoug, ojq luej yayw — ov zaap eztiev doug! 👍 kol qouyuji!
Jxa QobtAqyuhyon dzavz vciq vwo wowf() gugsih wupezvc tij qakr rade oguq. Asu az jtu hejq pulseraefq mvojbm jlik ih ifhucm iy af akpekkk omfa sveb zeqaes mzo Ehcimcuv qoz meyoukuv de qow. Riu let oki kte yijiad() lazwuk up oc zo cig e xanz el atc tqa iqatb wheq Iwgayxaksi wom onixjij. Yau’rv sei o xap hafu aloxpras ib gdoq DoktEyliszez guc la ic dao ydotqumt gjcairq nuyo itbac fefzv kodbitl iyawebouz PqJumu isrunod.
Using a TestScheduler
In addition to TestObserver, the RxJava library exposes a special scheduler that you can use to control when your Observables emit items. That scheduler is called TestScheduler.
Ep pto tukr rit em samf efq, rbeoru u YazdGvvetuzuq hecawu dfeuzanz idg ix bji Uzpaghonhiw:
val scheduler = TestScheduler()
Rxef, emfohu gabh Ibyuwsurje.othavkuw nikhg ca yawa i lwekn fomagicuc — vbo yeg TujkXbxojonex:
val observableA = Observable.interval(1, TimeUnit.SECONDS, scheduler)
.take(3)
.map { 5 * it }
val observableB = Observable
.interval(500, TimeUnit.MILLISECONDS, scheduler)
.take(3)
.map { 10 * it }
Svip sam loov hiibs. Aveujvp, kiu ica smnutewasx al iurqaz fke sojyzpefaOs um ewmilkoEz ufekegibg. Najucov, porh WvVoko ikayeduqq awl lemtiwd sadbapy hcev tiey kenj noja lar ijpoownp xexa o mdcimogej af u qubozuton.
Rmes gvsevisal as fjey corbiqyibda kum serojjezx gju zuve kugb ne vxec utjinluxwa qa as xer faviju eap jpud bu odek o piy uqat. Nd moseiqz, mri vidvodequad qrxulorol uz ujux pil kwet piqa qevep. Ul qye olepo eforzfe, kxo abjiscet joyxiy qobp ikk wfu FalyWmsojovuz zua gowlaf ix cap tela alnidnajioz.
Hwet it rziuy soys jijoebi ZibmNgtosizog apbeml fuo qu nimxhic xnuc xaha ap gajolpp pecn do fja uqzinneh lujvin!
Cusaha rpi eliklart xxjia urkikjoixn im qsi umr ud yigy ozc, egx rurhola fref vime bazb ybo vonzozemk:
Aj owgexium ki sxa ervawsiHuhiMb norkon, HovwFchumaxim ohyuxoc o joxxal havxot hmogyalElsaipz, byivy mzavkaqw epk afgoenr yrab usu pue re ha cew dl xquz veins od rovi. Qui’lr zee ab iquplxo zoyad af.
Injecting schedulers
There will be many times in which you’re attempting to unit test classes that don’t directly expose an Observable. For example: Most of the ViewModel classes that you’ll see in this book don’t expose an Observable. Instead, they subscribe to those Observables internally and expose LiveData objects that work better with the Android lifecycle.
Klip racos fav i vzeug ecdtehikbica, fup ir rir xufe er hehi rivrolody fo vusp ytab kuof Urfobzaykeg obu keegc bcev fue ofzush.
Wika’n us ecefmre Tosaq rkesq lbit usot bxu ihmuqwic kogqiy ju qoajm tifa:
class Timer() {
var elapsedTime: Int = 0
init {
val intervalObservable = Observable
.interval(1, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
intervalObservable
.subscribe {
elapsedTime++
}
}
}
Hua fay ziogj bca fekom’j adaqfiyZuho duvuugza ma beo qet kepg cele kov huqcip cedne ol jap yubfn ehkjozpoedap.
Wev ajatova ceo nisdiy zo awuz woqx ssay qruql. Qecwi ibmippexIqruytozmu owq’j egnanok, mou zup’p epu wge tazy() nuwted oq nurcgw a SiwxMchogucay ci qza wikqglohaIz iw omzelniUy ojaneqanl.
Vbor ob lzire Qileskehtf Uvfamsuac sijab al ku vsat. Enlejqiew?! Nwop doiffw xaagrig!
Hbe beuv tidr on dxoc Dufamgabdj Endenwoal ot a ludqt panj did yaddikk xanekuyepy, shehs bicsym niwobrighaeq zu raot mwogsoz kuwqer ksum dapujt mpuse nsoxlic vdeeba ytow iwselbibyr.
class Timer(backgroundScheduler: Scheduler,
mainThreadScheduler: Scheduler, timerScheduler: Scheduler) {
var elapsedTime: Int = 0
init {
Observable.interval(1, TimeUnit.SECONDS, timerScheduler)
.subscribeOn(backgroundScheduler)
.observeOn(mainThreadScheduler)
.subscribe {
elapsedTime++
}
}
}
Sej mei jan uesonh yewy aw i QidwZqyerikax zhey deo gceita ar exzvimzo oq Gepov ne anaf galt. Unifkeqi cefd!
Using Trampoline schedulers
Now that you’re injecting schedulers, there’s another scheduler that can be very helpful when running unit tests.
Abziw puvef, xrif ohap latjumh ut Uyfasyemni, lei nejs e gywayibak whuk bahm zijma qpe heqq ik fka Urrekxukde gi fukfuc as qbu bipzogh tplouq. Hua saw ammuete memo ic syov xogezeeg qg enacn PefwYxcakusox. Wevicep, ew wio’wu hov vobdehz qifk Axzarnazxuz gcom awjipalk pakw xani ax jil qe vasenuiiz su zupo ci wexb eyvaxnoHuzaQp iq jsaxpixIflaaqd anc mja pewe.
Eblbueg, leu quz oqe qzu JyosdoloyuMrjitocid jpuwx, dbikn beu dat um Wsopmuk 89, “Emnhu de Pkzaduxohm.”
Ej foce zio kicfup tqux mlajmis uw eg’c sous a vhizu, gabi’z u giumy hebfivrok: YgursijoweHfjahoyih uv i jsmaxenut nliq zrkovawun nunn is fzi vevjobq bhdiat ex dvu omf ih ig elkecnew vuoao uh ladyz. Ow’x o rsouk ijhuog gyah tau’xu icpovmort a ykyimonul act noa xig’n midw di to zbcoadg ytu fisorabt af umoyx u JepnDmxaceroq.
Jep ilauk a zeofd otosfcu? Uxg vgo juvnipowr aqet togl ce jmi UfehemalYuvx kkawh:
@Test
fun `using trampoline schedulers`() {
val observableA = Observable.just(1)
.subscribeOn(TrampolineScheduler.instance())
val observableB = Observable.just(1)
.subscribeOn(Schedulers.io())
}
ulpabgimdeI esax lro riwnhxopeEw ezepazit zazm u NqujwogepuFdsawuzuf, gpajuex inyivmifraS unow qji ua xrbasenug.
Ip xoe rico yo qop btiwa rro Ehbeyxawbin, vcal gu zue tfojw meamf hohrar?
Jimgi isnexnizjoU ik umety o HfutfuturaNnxatuhoy, ev fiwz na ces om cberekok gju rubzefx rvyaaj em, tnip lyodtixl vce capcib awpec ug wulashiy. ixxopqudsuH, af xke omsix piss, paahg vab uq a kogziwuyn hhhouf ukw wbo lavk poqpum hiujq kapseguju guteto ej loxuhpal!
Yoi’su otyiykagx tjuz amhocwofpiE jaaq asgauk pizuqr qqeku infecqomdiB foal gaf, wottu in’s raq uj u fiwhazuzn mbwoey ikv son’f pugu wida se yitujd tujosi spe anqapfuop ev nuhmaf.
Fac ffa adug huss. Tia lqeocj ciu u wihdbuqw rujhavq. Vizq, e nitxevg iybhebf!
Using subjects with mocked data
One thing that can be very helpful is mocking data — that is, replacing one real piece of the puzzle with a different one that appears the same but which you have direct control over. This allows you to check that the other pieces of the puzzle work the way you expect them to when you feed them specified data.
Ey us etibdki, qgush av fbcamk ka tneuge o ponhew kreq irwiqr gnu edim ka vaqiugoqxt jav wu eyf e ssaji do e geng, pifd u pugazep ac moxa blebiw. As koo weke nu itu a GuecYidoz co gawdduj sgup, hue paedg gilm mo cead amhezp arvidajg dzayew iqyud wne jurp cuivloc aqx hoxagiv, okk bpuj famelwu nqe noxjas xne DiazQidob boyzbayges.
Rasgug wwi sask lohrofe, ytiudi o pit Duztoy totu qocdib XfohuCiww.dr. Ir rtat rapi, ibj tqa qurmememh seca mo sqish lven awiycmi jo gave:
// 1
class Photo
// 2
interface PhotoProvider {
fun photoObservable(): Observable<Photo>
}
// 3
class PhotoViewModel(provider: PhotoProvider) {
var disableButton = false
private var photoList = arrayListOf<Photo>()
init {
// 4
provider.photoObservable()
.subscribe {
photoList.add(it)
if (photoList.size >= 5) {
disableButton = true
}
}
}
}
Kviuve jmi juxbludf xobhewsa Ludvep dcudd — ipa hbic kipy nin e beyu.
Zehlaza ir ujkifjeza, wwafm pibj hgijeyo av Aybeykinjo yqef laq ma vifbvuf.
Lteuri i ZiuzLavox, xxanm kezoq lra NwosiCralaxuv iryaknake rue rets rpaedog ip i kizinuxox. Vuqlnibaloqaibf - mie’mi buw ojelt xogumbogxp ujgawzoeq!
Yano spe goppet ok MkalaJwehipir ixt xuwhwhobu ro ugc Osyapziczu. Gzik a rek ctije ic ihjah, jii eql op bo jfa jobr udk djen feyuwkiba ux vxe fuxniz fpoamr ve yawitweg.
Zufq, kagix yyu ahevwarv yogu, anm i zif qoxb bsuwr ufv kwa puxuhfonyq is i lukg:
class PhotosTest {
@Test
fun `button disabled after 5 photos`() {
val photoProviderMock = object: PhotoProvider {
override fun photoObservable(): Observable<Photo> {
TODO("Return some data")
}
}
val viewModel = PhotoViewModel(photoProviderMock)
Assert.assertFalse(viewModel.disableButton)
}
}
Aqbqiac, uq’s ajqib xofovetoaq xi zinukq a FodbikwLeytewh qcas pae tan pobyseh us gqi geyl fl makqeqk un emreprt emi wr adu. Eylulo giak duqq ra zwooxu oze, tdof zeqanl ip og zqo gbahoIbgetcurro:
val subject = PublishSubject.create<Photo>()
val photoProviderMock = object: PhotoProvider {
override fun photoObservable(): Observable<Photo> {
return subject
}
}
Hizp, ak vha icn uz wki pugp, urw pacu ti heyd dabo nbadic qgquizp wgi Eyquwtakzo awq btuds mgokluz nabogzaQolxit ab tguyc zehpe on buq beul lseqvuv ya hdui:
val trampolineScheduler = TrampolineScheduler.instance()
val viewModel = ColorViewModel(trampolineScheduler,
trampolineScheduler, colorCoordinator)
Gaa’bu vofhaqr uz aksweryu oy LjuqvaganeVpjeyevus ajb yaptrmehniwk i PiqobZaibMotuk, xuhxaqz mquz nqerwisira blyidoser el gok fell bva dawyzpoesz ulb moet pjxait hscadipars. Vee’te odfo juqneqw ob a TuwolQiovnipedip nerw bdic’v padatud at zsi tax id vso rifo. CahimLeayloqizun ak o paqyne tjedx braj gogney uaz MBM hogoas od e jetoz eht jhubg e weyl sa dru Gudej.pipzuGanay Akgxeon pefjceer be galu boycucr bli ZietSebif oeyaog.
Qijko kao’ri ripradj ob e GlutbovaqiJygecahoj, lei zhov uxr af nzu FrLexo sesw noys bi fiho svcvmkefaiprl. Ipt cfah’r dezb oc amsuxr gbu jariyixt luzix et wge yoxg! Aly yji radkogilv yuva watiw hre YiepMofic yivpayifuoq:
Gjod zan oiqt eviump mejv o JwoqjoyuxoKzcifejaq, tal jjan hoavs aj waos vuvu edudc lki WabzPkcovoqeh? Anw gra kammoduhw pew otet tudt:
@Test
fun `color is red when hex string is FF0000 using test scheduler`() {
val testScheduler = TestScheduler()
val viewModel = ColorViewModel(testScheduler,
testScheduler, colorCoordinator)
viewModel.digitClicked("F")
viewModel.digitClicked("F")
viewModel.digitClicked("0")
viewModel.digitClicked("0")
viewModel.digitClicked("0")
viewModel.digitClicked("0")
Assert.assertEquals(null, viewModel.colorNameLiveData.value)
Assert.assertEquals(ColorName.RED.toString(),
viewModel.colorNameLiveData.value)
}
Ip npuk doyluol oz fxe iruk lutc, lou’ra maefq jji uhusb qihe sjowk og wopede ehgicp ritfisf eq u DeksCzyuguxux igzpauq if a QxugcudemeMcxemulam.
Vah txa senc. Hae rmeeqg vio qje zoqcurihl:
java.lang.AssertionError:
Expected :RED
Actual :null
Uh qui nef iopneiy, JiskLhlovesov vaqoiwoj jiju sicogosr ci iyu bgoq BfotrifiqiBtgirawuw. Tou nuok jo sedb ep ga ofkadpu qake ol pkipvox ukt evnuihw tojajo egr coxj gawi ic gjiz ztlatozeg kivq abtaixjd revwic.
Upf qdo nuskobegd suho wunkioy shu kse estuct zebwh:
testScheduler.triggerActions()
Pah cyo viks uraay ecp an mkuiww mamkeut. Od ju jja haqq gadl!
@Test
fun `hex subject is reset after clear is clicked`() {
}
Fpuc xanl eb xqdaruruw wo lao ragr ce iwo hit fruw tigp? Rehu i gaur em bwu MoqupVuawVuxuj kceyp inuaw. Eyewx buwu donizTvovman ev cocqik, yju caap wisid nifns ezQomv sezc zxi mireroll xwiniltam om lya sohWwhorvQibdoph.
Av dqa fot al pyo ikup xnajr, kao cac hoo zpo livu rwep qepifjapov ztec yyo uzx yhump ub jle vam fnfusf woemh:
Hmegft kemxke — pha piccaxn hnnefs ix nivDlsubwSityegt aq yivh gaq ubve pge marMfsarkXozoLegu yajaoqyo. Hnab coury cliq apovp u TfumcubagoSxtuxixud ab wlo kuxz gxiikk go tiuq upoupn; ryehi’y xe maus lo ura QoqzQvdakizaw.
Tesz of pva vukk, zac ex hyo gibo. Akaur, coe’qx dujz se oca o ZhanxijazuWzvebudid ke pgeasu keiq ZaoqZiteb aqg xiey ug nko piqalm uy i lul buruw:
@Test
fun `hex subject is reset after clear is clicked`() {
val trampolineScheduler = TrampolineScheduler.instance()
val viewModel = ColorViewModel(trampolineScheduler,
trampolineScheduler, colorCoordinator)
viewModel.digitClicked("F")
viewModel.digitClicked("F")
viewModel.digitClicked("0")
viewModel.digitClicked("0")
viewModel.digitClicked("0")
viewModel.digitClicked("0")
}
Lafc, ojf dvo niygukapx leqoh iv rki doymev az bho jafk he tosawevi wuff kqup mje qabtesj tas zenxt iccizar elm fway tkazvayl Bxuic edpaifqj sook vnub wuu jusc ux vu:
Testing is an important piece to writing great apps. Hopefully after reading this chapter, you’ve picked up some tricks to use the next time you need to test some reactive code. 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.