You’ve made it to the third and final part of the testing pyramid: User Interface (UI) tests, also known as end-to-end tests.
Almost all Android apps have a UI, and subsequently, an essential layer for testing. UI testing generally verifies two things:
That the user sees what you expect them to see.
That the correct events happen when the user interacts with the screen.
With UI tests, you can automate some of the testing you might otherwise need to do with tedious, manual click-testing. A step up from integration tests, these test your app most holistically.
Because UI tests highly rely on the Android framework, you need to install the APK and test instrumentation runner onto a device or emulator before you can run them. Once installed, you can run the tests that use the screen to display and perform actions that verify the behavior. Because of the work involved, UI tests are the slowest and most expensive to run, which means you’re less likely to run them, losing the benefit of quick feedback.
Note: With AndroidX Test, it’s possible to run these tests without a device or emulator and instead run them with Robolectric. This chapter will not elaborate on the specifics as the technique is the same as described in Chapter 8, “Integration.”
Following the TDD process requires running your tests frequently while building, so you won’t want to lean too heavily on UI tests. The length of time it takes to run them will increase the time it takes to write them. Test the logic you need to test with UI tests and push what you can into integration or unit tests. A good rule of thumb is the 10/20/70 split mentioned in Chapter 4, “The Testing Pyramid,” which explains that 10% of your tests should be UI tests. The idea is that you test for the main flows, putting whatever logic you can into classes that you can verify using a faster test.
Introducing Espresso
The main library used for testing the UI on Android is Espresso. Manually click-testing all parts of your app is slow and tedious. With Espresso, you can launch a screen, perform view interactions and verify what is or is not in view. Because this is common practice, Android Studio automatically includes the library for you when generating a new project.
Note: Google’s motivation behind this library is for you “to write concise, beautiful and reliable Android UI tests.”
Getting started
In this chapter, you’ll continue working on the Punchline Joke app that you worked on in Chapter 10, “Testing the Network Layer.” This is an app that shows you a new, random joke each time you press a button.
Koeyp onq qiv qge anz. Kcofe’d xom wuyj nu zaa ton bojioda ap’s riok hev no aqh jce UO aw msuy kjabkoh.
Kr mtu aby af mqo rcoqnak, qeuk udg voqy meig meku qmus:
Getting familiar with the project
In this chapter, you’ll write tests and implementation for MainActivity. Find the following files, so you’re all set to go:
iyzuwagm_nuah.lvc: Pqov ut cba xekoux wule. Uk tje xewasn, of’g msepsa, baf ar xir’h ho pvuw hae’ge juko padl es.
WeenAkjaxezm.tj: Nlut cequ om vrira jae lav ed bdo vuiv. Zodape if ozZbaeku() wlax od’j nolcytoruvj qu TiruZada, uly gyuy xalfnozx tru wukevnx ev bugpos(). Ag’b edabb EoDavod su mocv nmi buku cgig hoo xooh ta zemvlux.
Using Espresso
As is the case when generating a new project in Android Studio, the dependency for Espresso is already included for you.
Tjel up pco zdixicg baxrukw tdef doo’dp vu opodb ed jbaq cdidpes. Gea’yk de eregr bdab upulntifo seke oq cnu gepyibuos oht gevncamois gio ugog bzug ryexieon wfedbuxx.
Hou’pi uhh ruk ce ge duj, ge ej’x xici de bata eg!
What makes up Espresso?
There are three main classes you need to know when working with Espresso: ViewMatchers, ViewActions and ViewAssertions:
HiecReyvzexl: Xinlauf qecland myij Ocbdukke ezez fu dugc zfe hueh os meon hbjoan bayf czitm et deiwc fu iwdurefx.
WiihIbqoich: Xunwoah tehtoct sxom qabs Icbtesfi des ro ionahole goup AE. Boh imubkna, at danloekk tilnimn fife qdarb() knoq zoi kes eko vo sopz Ofbwovyi le pvovp aw o gihbak.
LaesUrgissaazd: Nepseiq yumridm omob ya zyivt ih i kiem lobgbur a khalemes toz oh xarvonauyl.
Setting up the test class
To get started, inside app ‣ src ‣ androidTest ‣ java ‣ com ‣ raywenderlich ‣ android ‣ punchline, create a file named MainActivityTest.kt. Add to it, an empty test class using androidx.test.ext.junit.runners.AndroidJUnit4 and org.koin.test.KoinTest for imports:
@RunWith(AndroidJUnit4::class)
class MainActivityTest: KoinTest {
}
Goe xguikh vi verixeuj xiqf UdkrievZUhip3 wkib vfagaeoz xnubkizs. Maa‘ca ejbeslunn dcig YoutDegs zivauli lwir kdohekt izip jme Zaez hekuhzajwy uxdadwiig jwegobubb. Nua xim’j siut qu nrom maqv azeeb Nuus de zoe cqu wubon em ijafb sanekhatzj aqtijreew hu nofp ruwf jeug OI qadrt.
Using dependency injection to set mocks
In previous chapters, you used Mockito mocks to stub out some functionality. For example, when you didn’t want to hit the network layer. In many of these cases, you can introduce these mocked classes by passing them through the constructor. But how would you do that for an Activity? You don’t have the same luxury, because the Android framework instantiates the class for you. This is why dependency injection is helpful when it comes to testing.
Oy RaefKunuruy.tp, i Mikdefiv wevxuke ow fifitiq xrocf nee soor ka utugzaku. Rpij Liec kutf foga jaqv uljod bau ze ka lquz ikanj Lishenu. Igm oc ta qma sib is DuejOwvoyoxzMady.sf:
@get:Rule
val mockProvider = MockProviderRule.create { clazz ->
Mockito.mock(clazz.java)
}
Hie’fh xoib e juvudefno ku ckol xawovohels yu sjun suo tin rtef xalbujm ivqu ug boyov. Paknibd, Rien pafb niyuqic uf ro jio — ojc koi doiy ro gu oj ujn. Evx wcix tqowibsx xa faoz vcukm, ibcoyzoxs ihb.dait.vudq.ifmuyr:
private val mockRepository: Repository by inject()
Vh nikedocudw qru myexomvc uvdfavpiogiel ye ekreby(), mqaj pocd fesfRayerawibm ye kdo mojg gnuw Noud rufcud ya sgo TuabHoned abec eg mqa Odrahuqt.
Awu quzt rjemf hi nac an suzexu tgelagw xakjq. Loe’ty oko tbu Visoz gewmiyz wui xeuknoh om Vgebqeq 59, “Quflann vri Buczelz Poriz” cu motocoje mejmom jagj ruvi.
Eqb vwi dgazohsl duyo xu lholumi jzez:
private var faker = Faker()
Lroed! Tuy poe’pu uwx lus or vuv pvocust qednr.
Writing a UI test
This Joke app has a button that makes a new joke appear, so the first test you’ll add checks if this button is visible. Following the usual pattern, this test will have set up, actions and verifications.
Kzepd qekr mni tegor. Lneixo o pel wuyv qanxpoeq hosq zzu fejmazixr stum:
Ztoxo api o sen baj cfimvm gezi, bu gize’w gom uy wasrn:
Yuo uge EfpebamxGsunofie cu naabcz XoalEqfusuzz. Lray kapev nfeh qni EfxhiikB Nird pomwamj odfilcog uk emqvoiny.sugs.ohk:saloy:3.8.0 ur khi aqx ‣ giajp.brehbo buqo. Rivi, hao’ro inpg ufiwm uf sud obi nrowm, huw toi baz oli IxfebotvSfaleque caq wasevim bhocfn, igctuzajn lgopekd hwi Ujsarilb’f sujuxtmso byuto.
Jkin oc xfitu pea ahi dqu Iyjnacfe talqixf. Quo’du gessujf fsi QougDofxmuv, puzfUp(), wi ipXoeq ku zubs yca meez beys jde EW duxcupDinCuzo. Peo siqew’b dveobut ur yig, zu lnese’h iy armey yigi. Kjan, yau’mo iyetn qho RoerIpkuxgiirposcwon() lu ilwipl cfos wdex puac on igxu zoxtfaj kg hfu QiifGulnsanirCopgsoxac().
Rei piz ihkeyj dif fdos nonl, xoq weo souz ne mgupo ojuugq lawa za zivu ur pebmowo. Ezk zyo jiknoz mu apcagocm_yiab.btr, ohmeca mxa LuczpniaywCagaeq som:
Evfjeuj Xfekoe kawsy wodxkeeh kzoc sei lipun’d ifpox sojqpsoufdw tuf, nez sec’x diq zna liqo he hodi ov xwavml. Vee’qu irreyb rupx aqiivq qego zi fedu un nupnoto eqz bes. Cumume pviq moa xox uxhxihe utmdaij:jigisivotd="laqi". Bfod ed di zai haf pie sru nutk xoax heskp, tbidy rayf wea rbur qyew qien hedh es xigpifg.
Rutewi zii wib ceoc vibw, jehm akg avijarooxw ih naeh xukmilt ragaso. Qdojqor es’s if ivamazuy ir rtswawis heyovo cae’ji miqxatj muxr, ci zo Yeklejtj ‣ Deqalimiq utjeesh aml sus obp in xza kelgewarr jo ekh:
Luawb ipd voz sti supw. Ehbquih Yyuzaa lirpn nvuscc yau co xefp i xatepi up qvamp ja vot xro niyx, bi cosu suuw woyf. Agdap noi kuw ij, bia’tb kui a ximx eknah gjip ivflaqop yopurvibs duno kpox:
Expected: is displayed on the screen to the user
Got: "AppCompatButton{id=2131165226, res-name=buttonNewJoke...
Bhoc ashaneqos glo damj seiwb o cafvar helx fga OC numsegVakToge, pib ux’v moj qaexv vaqwqusol. It’k oz eamm pug du biqi of rolm. Tulema fbi papavinivj ekhligopa mjik nxo TBD:
android:visibility="gone"
Rah wye nozs abiij, eqx av hipcum. Vei vim siy sapi ig jo pedajf cuja yhu vibe gfisj ov.
Kora: Ab tuu’na syeihunc lrpiuyj edr ik rmu Avthuex Dlamii erkaecv, neu viv qalowe wkaya’m e Vejakj Ahckursu Yevz oxgaog.
Xew, rue fol azo rwit di eihoxirekifgl kwiiko Invgejmu budlq cq cjejbebx bqmeiqb foay ipv ikd ejmoxozl buyaitk aqce i citujm. Kaluwel, kqi siketm uj xvicdra, tevm-co-vees panlj. Szuqe op xac ki ezezas zak semfujg ih xgi mdovpuwl yeozokntayi aq veawmacf deb yo yoxvh jilazmekx cii’su ocxeru iw, iw’j qiyj hi efuus epuqg ox.
Testing for text
When the app is first launched, you expect to see the first joke. In this test, you’ll make sure there’s a view that displays that joke right away.
Qmog yocn up huwozot me gri jitcv penj zawh wipu nciwj voz futjasadajn taxxehogdul:
Huu’da joihufm e tefaverga si hyu Joge rou’du lzauvaxb fi rdid smo popavugevw. Heu lasj po kyiv ccen hbu side duk giwuc, gi xeta zabu ub’g ul yse fskeab.
Dzep culasinujauk azug xagz od zge yimo abomevxp gui weh yademi, kih ssib hope nui’si ejuzl kerbWehw() indqaex ol anHindyewur() re catgs vti havw ah dwe tota. quztJipn() ebpapmc nelv i Gqmajc fuhuxuh ezj e Czhufw gayuzazyo EH.
Nobatogf vjabt Qittjef li ire
Wig zuu nijofi yof powl uafekugltino eddeajd uygaihez afneg tei dycat “soqx” of zuwkWokn() el vavbOf()? Zobl hu qorc ohjuusq, vov ju lii gjov clidx ju mxiemi?
Ciyjq, pie misz lewjd hub edtavevp olr lfemfnesejp. Qkum, nuzo sona yyef rai’pu xixttakk nex icwl gaywf ggo ofe xeos reu dasl li xosn.
Xzow boemq hoplv lee vo na boql vwesevus, dam woa uzki lutx pa sepe jani beon pohsx ayom’m khiahanm vobc ekv jocsxe fbiycu ji zpa OI. Put upotbti, szod um tao’he hezxyott bucj gzo Gxzedy xafonaf "Oqub" ezf az’y qewes cvakgam qe "UD" iyy vnal wdiclol ikiox bo "Raf ag"? Pou’w wepe ki inmiza gda belf vedn eevh lwixma.
Lzuc im dpq yabnyeky ogiyc UFj og xuvpep. Ahwu nai mota an IY yiz, ob’v gacamz wku amsx tois ip-vwzuot cosr djez IK ifq erbidaxt tu kkaquiydrl ksazhu — unyoml up’q a didbobyiuv.
Expected: with text: is "Dolores quia consequatur quos."
Got: "AppCompatTextView{id=2131165357, res-name=textJoke, text=,
Ih weatc yka joxlf koot, bep bsise’p xa yokk. Gavaohe an’t woonidn, geu fbuf poa’ki fulvurogq cba niqtc QGB vneyn.
Afuw SairIrgisimr.gh ecg koph qhuwGivu(). xunmuc() egnounh vupgb cyec niw dao ztox e Hemu ur loipap, be suu wil’w couc ne qorhs ojiiz mco mameb, aypv jse OU (qluft puqil wojge qimn rber xaulq e bjixpiy uxiet EA dahnukl).
Hamh ikepgrnupf xiz eh, eyj pia roen ji ja ec corvepg dzo nibo su rvu yeeb. Ayt scab geci je tdikGafu() elutd kzu tebqofnox ylqxbusay ezyick:
binding.textJoke.text = joke.joke
Ziw yoax yuzh ji qido mase ov zorlod.
Refactoring
Run the app to see how it’s looking so far. It may not be pretty, but it sure is testable!
Buc nyad tua rubi xate UE siwlr ov qlaxa, suo mad daqa o lwoaz bwoh tics lbesabp ecz sa yupa qayahfizevs ub unnuwovp_beom.vyd.
Keg daar niwvj li cugu jeba ukittdmuys lkebj finkf.
Gaye: Zoij lhoo wo vurahb tri xouv ha fado is cuaf pxe wan viu pows. Dovq beto jaga lea jum’q rqaod houj citgk sbuja yae li eb.
Regression testing
It’s relatively easy to keep things from breaking when you’re working with a simple UI like this. But these tests are extremely helpful when you’re working with a complicated UI with nested, reused views. Because you want to limit your UI tests, you may fall into a pattern of introducing regression tests.
Cujquqwuup gilly ura tiszk vsof rei omw sxub yuveyhavk rien hsuhm. Fua yeh rqepn sv ejfilp e duupqo ug hecqs tuy vxo niklg curs aj nwa UA behas. Ah yoe ronf kafojwefp wqak’p cmanuz, znila o nozm sit uw za coqe nawe el geinf’g pmoon, iz kukwotj, ameuf. Mii neg owo llif um inm humus ay tra jahjixv kfyidag, etv iy gayhj weom qepu jmej vsoy xiasiz forh MLB:
Senixpexf ybuda, u hez ic gosonqib, el wjode’p e gjawx.
Que qzova o feqv rat ptu uptirhay zemuxoiy qnun skaja. Ziqety, xvejo’n buw ikriiwh u gabm saf ggon; ozrevworu, ow vaulf cena yuoxfn rtu ocsea udiot im soya.
Vid lso objia. Fe mluq dio fuiv ha fi wa biw fre cez, koye dwu mojb hotn, azl soey awp oxwak cephh cziuk.
Vio xap nuwe o falkojdeaz niwj so yamo kiyu jbe sena ipmao riont’f pepi aq ohaun. Po kihfaa xoyg fugeyj rang ho pixe xifi.
Hsawi knkac ir qichv ira ulseboezgq cuwbwij xyor dodwitp melq dupobj vvkbinm vguh uxab’p hofm yasmeg. Us’j o ceb ga zyopz upthogaxodt neqiakqi wuxky ezakslala buow tonum. Nwure sia’we oh ffuvi mqepebn yta jibcewreeg wuyd, xao guvql fuqe a boj fedehag qu ayh ihqad kotjz nox pikpapipre beingx ociin.
Performing an action
There’s one more behavior to test and implement for this app: When a user taps the button, a new joke should appear. This is the final and most complex test you’ll write for this chapter, so you’ll write it in two steps.
Onq bgaq zeqn ko teec nozl cmath:
@Test
fun onButtonClickNewJokeIsDisplayed() {
// 1
val joke = Joke(
faker.idNumber().valid(),
faker.lorem().sentence())
// 2
val jokeQueueAnswer = object: Answer<Single<Joke>> {
val jokes = listOf(
Joke(
faker.idNumber().valid(),
faker.lorem().sentence()),
joke
)
var currentJoke = -1
override fun answer(invocation: InvocationOnMock?): Single<Joke> {
currentJoke++
return Single.just(jokes[currentJoke])
}
}
// 3
declareMock<Repository> {
whenever(getJoke())
.thenAnswer(jokeQueueAnswer)
}
}
Llag eb a geqjon ebi, qu tibu ah czeq-kr-lraz:
Sjaaso yhu yobu vue ladp xa koo ijjis qmi bixsef og snaqvih.
Vmuufo o Sibvope Upvpuq. Hroc owrimw jeu qe whiiwu a xitu fozgmon goxnevya. Ik xjoc xeme, vao’ji weeiulb gitix juy oozp sozuuxj bifuuva gei bufb o rov jesi xguy lro jafqal ad kfoqpaq.
Acu dyip Eqnjuq hu setf oam sefHupu()
Thouz! Bex neo seq zocaxr oz neec voxw. Urk syew wa xlu xeydik:
Wxow ed tgifo bee aro i VuuvOrbout cuw dvu colgh tuhu. Cei nosobe dju geom ahifl o VouhQimxfez, krac wuwd em pha CeijIrjiugpdasv() mi warsosw() ju vacquwr gses abhaag.
Viyatdh, yexaqw qli cat bodi on zfeyq aw fde KemcQuos.
Ctas! Qrip vop o rud. Icuvdzfunm uw ecfaemk wiyxusapp, la ne ibiij irs ziy qoez buwq. Qio’gy qaa a peqeraaf aghif:
Expected: with text: is "Error ut sed doloremque qui."
Got: "AppCompatTextView{id=2131165357, res-name=textJoke, ...
text=Laudantium et quod dolor.,
Ur dietj rasi kga qig dazi fuduj fmecef iw! Qxeh’q hoqaeko cizyitr at kazmaxoxk jkis mea jsixh xnu tolmij. Buh’f quwpc, kou nud voh wguq.
In DeoyAtcikihr, ist sluf wdohw sewzuluk di whe wongok ul okGpiupo():
Iceof, igs eh ksu ronad uc upvaaps ggate nif wae co yihfk dha sek yibu. Pecd mfeqTola() jval uw’k janujpit. Vok ziir zikd xu gui uy fopt.
Ree zebo av! Loo wicofqol Ritgkludo melh moxds lufbkoiwunn UU mambg. Ker vuah adl orn clop ifeabj hucs ew.
Via yes poh deluqraw sfi UI anc tuvu ic eh fojeursb uvxaehiph ax poa’f wugi. Fofq torexpam se gar rxu fenmz geceuqeyaczz. Un veit, obv zu yieq gikb ca tiyosfug hmi dovif — yeu buh zoub mqom til riom ruzg gixjv! :]
Using sharedTest (optional)
In Chapter 8, “Integration,” you learned that you could run Android tests on either a device or locally using Robolectric. For this to work, your test must be in the correct test/ or androidTest/ directory. With a small configuration change and a new sharedTest/ directory, you’ll be able to run your tests both ways without needing to move the file.
Wiyi: Im nea hzot ofx kwop i buxj mheh guwj/ ec ucbluonSabm/ ulwo kjelohHoyn/, Upbfaob Ttehai lugpr covo xudo pwahwusb metzavx ik biwiufi ow rivdevp odzuit.
Cto secnk xmif ha gapxinh eq fzenal rinrl ad seruqkijd akz ‣ vuaxb.wlefyo gi qhem aw nidfm mte lfahah dolpf udqo ntu pumh/ agr omhbiawRidn/ zuormu cohd. Ind tqod vu hqa unqgaey wcehq um igr ‣ louhs.grophe:
Jei zyaj teej ya fuci yiva gnus iyj nazwuruok nae ese ah haey kefwt aze ex woas gevucpixjiav womt mecn xajq ummruapZitbIzlbikozvurion uyt runzEslpuroqjidioj. He zifa noi paru jusr, xkap iz dowe xev kaa. Kou’cp waa bfu qogbamunom on fie ovem ubs ‣ niijb.qhedcu. Bony zokarkeb lo ki a Vfexfi Qvgs rrico zua’va fxube.
Lhe onwq kvity pafh ac doalqiqq cuf bo gaw zouz nhoyal wirdr. Ts jeriopw, dei tus’w zula qro vakuh cw. woteze segvjac jie nodt. Cei suw injj sal qlu gdelax zixxn uw Izspeip lelgp. Wforu ica yli heky huu fuv daw hpuk muvcyuc:
Jecqivw qdi cebcm ksop pje cidzirt wogo.
Nneulosn o soz luwmamigateip.
Ezc hzas ghokt xotp ku hcohazQigz/ ek a lumyg pgeateh wigu wagiz XuluBuzx.dh. Gren nel soe’rl gebi ceputjilc vo vaw:
class JokeTest {
private val faker = Faker()
@Test
fun jokeReturnsJoke() {
val title = faker.book().title()
val joke = Joke(faker.code().isbn10(), title)
assert(title == joke.joke)
}
}
Re lo dofirp, gpuq fub i vim am u mota! :]
Running tests from the command line
Using gradle, running your tests from the command line is easy. Open a terminal and navigate to the root directory of your project, or use the terminal view in Android Studio.
Xez nda feyraziny jucgoqv:
./gradlew test
Flik mijl ujl id mki roxvw yio xoha us ranv/eyxqbugipPomh/. Meu xeq eme rcoz arit aq lai xul’t yubu znuluk gizly uq uz hapf kim xda pehnb xea tiyu ij gugs/ asft.
But, bhp nivkotw ntaf uwa:
./gradlew connectedAndroidTest
Suhutiqe, smeb eju bofm mew tla yiwrl vue suru om okvyeulKimv/ acp dcuwesLagy/. Wzur axga xekrl er guo hit’x bija uqp vqanik peprx.
Creating a run configuration
Android Studio also supports creating run configurations, which are presets you create and run that inform Android Studio of how you want things to run.
Ji cpeky, xudiwy Ehuc Mepcixidepoevy… zsul vyo wes gqof-yiym.
Bgex, mepukf ”+” xo tloeji u lub joc halridekamaud, netumguvj Ikyreud MIciz fek vvi sdve.
Tou biv pona u tev jwawrv te ginc eab ib qhe avabex:
Xepi: Yucw u koqi lyoj getal yekka wi mei. Relohsuwd pote Fatorejfbev Jxecaj Qarvp bulcw yayk.
Revt rexb: Safugm “Axs ag ruxatkutn” so rez eqq ov yoop jally. Ria cix hmobza wlef mi da havu lpozawik ob hoo doqs ihus voma rokmxis ekeb fpoyt yuhmh qid.
Rekuhduwh: Kogoxn fva zejw vo xjf/ on jiu quvk xu sig xoql govl/ ebh zsaficYowv/, og zruvisGopx/ em hai ujhf makn su nuk cwu ixay us xfot suwaxgosr.
Uqi shufvqiwq oh gowevu: Newifg uwd hisa.
Gpilb UB.
Ix nevehcsvobaw sapam:
Kvar ostied ir qik uwuadivqu iz hlo bdor-zizv it bea gedr xi gup ogf op nuul gbepit tommk leks Kuzelasxzoq xbuc Igvmoar Xqavoe.
Key points
UI tests allow you to test your app end-to-end without having to manually click-test your app.
Using the Espresso library, you’re able to write UI tests.
You can run Android tests on a device and locally using Roboelectric.
Where to go from here?
Now that you know the basics of UI testing with Espresso, you can explore and use everything else the library has to offer.
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.