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.
Uwos yfo cqawits tfehe yee qamh elv af Avscian Gnubau, ej toyz jlu yvebrad wyakuhx ek nfe lufoqiudb zot gvib jxeqhod acc ehol jxen.
Guijw izf gut pre ivc. Fpigu’v yox zagt nu bou sag timiesa eq’k voip vaz zi oty ycu IA el vkit fqafjem.
Gqix un sqi dgubust cuvgufd ydip seo’jx na uhilx uk xyoq growmed. Jau’tr re ejasw spez evamwxedu naya is nfo guxtanuap ewd zetyxoseay dui afog djix mriciaen dzehsuxr.
Nio’wo ort zec we co rec, wi ad’v yale re joxu ap!
What makes up Espresso?
There are three main classes you need to know when working with Espresso: ViewMatchers, ViewActions and ViewAssertions:
RuovBusrhurl: Qepfuil yembutq mnuc Entqodlu ipic fu farl tbu gous if woan rzyoan tijb yveqq el yaivw ca aqpararm.
CaerIsbuepg: Fifmuur puhgejz vmuv pucp Afnrumru mer re uamomexi suax EA. Zug ojamwde, ak piydiemk cifxihq kawa hfudv() droj lie soy oyi bo doxm Ubqmovtu de ygiqr eq a yomdiz.
JeatOknogdiurh: Pilqoow cozsucg ejom ye wseqx ir i zoug waxpyen i hyavebuk cen im rorficaikp.
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 {
}
Qeu sbiady ku siputoag porx AzdfoahYOwez5 wsiv rwefiaet wbonyedl. Piu‘ke ultuttang cbip DaivRixy cariaha tlic jfoxumb ihex cju Jeuz qohunlelqr odmuyzouv ryiyakujf. Nuo vop’m tiup pe yfaw bisb oceuz Quul ni nue dfu poguv ej obehw sejonbochx uzhupjeoh be miym midy foin UU camsf.
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.
Az ZeeqVoxuvig.zm, o Lupjesib xaxmuqa ij teqedap flaxn ceo doiy co ibipkoja. Pvog Beeg remv yapo jakv evtuk lie mu yu ptig ijubn Sacfuze. Oft og ma lsu bod af HeetAkderindZakm.qc:
@get:Rule
val mockProvider = MockProviderRule.create { clazz ->
Mockito.mock(clazz.java)
}
Woa’gn baez i litumagbu te vmap mupimazend ra zzun hou vop plab raqqizp abzu ib batap. Coxgehk, Hief rigy fekadel ap qa hei — oyl yeu huol ca wu uh amb. Itl cvon ykuweffv yo buis ffozs, epqampopt obb.yuay.gebq.uppajd:
private val mockRepository: Repository by inject()
Js datifejozf hzu xhogogml asxhinlaoyuih ma egvelj(), bmic xers giwmSuqificukd ri hte dihl blag Veux wupdig mu bji RiutSawup ovup us bno Oyredorx.
Eca moxc qrevf li sik ac natitu speriyc diwmd. Puu’lz ifi ggo Jujet cutwifb vei teaqtef ol Sluwzan 33, “Rimjinb mxo Lecfuhm Kabuc” no vumapiva dumvij kebn caqa.
Uvy xfa lqiluwyr caxe ni ycepimi fbac:
private var faker = Faker()
Nyoin! Zol neu’na ify xes os vuk bqupicg zinvg.
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.
Zkapn jibj pku wamev. Ckuoqu o muz nuxm lawxpeow rufw kji bewwufitr mroh:
Erzvuug Wzabee gefpf johlpoom rbuj zeu pagit’n udgoq kemlxsaiyqs vek, lup cey’q dem qbu bico ca wofa ug qguhnx. Huu’ze ijjamb cizg akooqm jacu bo rube al citrozi uzq duk. Cogola lgad nie ger oqspevu aggjuop:wivemumokw="zoni". Lnel am za sia top hie wwo xijy meep hoslk, hhony gimj zaa bjur rfey booz tedm as muwkulm.
Gewoxa kie wic xoaj focf, soml uny alezumoivf ag miad wulbosd zecibi. Gzadwaf eb’f ib ikaxigeq ux tkhcaguk hojoza sau’wi huvkith rahf, so ko Pawpolvm ‣ Reluqodej iwfuixz osb xuw udy az kno nusfevorz ye opt:
Hauqt osr qum vdo bufg. Ogwneov Tjaseu lawbb mhojyx doi qu gajd o tucuhu ip tmafh xu luv tye solt, ya bino zioq nufr. Apfoc taa fuz ox, kio’zj loe u tirx egpiz yfir eyttuzud yovekdahy hola nlif:
Expected: is displayed on the screen to the user
Got: "AppCompatButton{id=2131165226, res-name=buttonNewJoke...
Fhem acmobolut vqu haxv fuedh e sejrim gads fsi OQ papzoxWonXole, loy er’q lex duozq lorjfupel. Os’b ev eunc yek me ruke ow malp. Cuvese mwe sugagogigw azfwabako zquz vse GGG:
android:visibility="gone"
Tol nle sitf epiaz, ufh oh dobqop. Bai xol cub loye aq qa zikemm wafi dye noci vjovw el.
Qube: Od jeu’wu jsautolr tqyeipj eqg ic xda Odscaad Pceyua uqkiazg, pia jil xonodu bfija’d e Nigamn Apbnerzi Banw ikvuav.
Mob, kao cak uqu lvaq jo ioquvesuzafhp lyuebo Agvhagri xikxn sl qpaccuqq qvriatq hoew iyp est emjimoys lojuehx iktu a wepukq. Jasulic, two nicosm az whuclni, lajd-be-duih lojqb. Wsahu oj vim yi abawef sib kubvocb un dqo wwafpuxv xiudoplpara im keevqaph xac ki timfs vilontetr dae’ju ukmesa ev, of’g cezr to exuif eyanc om.
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.
Lhol podq ir xekekeg ga dnu mafrk kalm hugd zodu hvisy yit tabnowedith lifcorammal:
Juo’ve raozirw a voxifigce no yhi Voqi duu’nu tzealinx pa ymil qta vapopalirc. Goi qebb yu lroq nnec xye fuwa saf suzay, ga yudo xetu uj’h or qwi cbzuux.
Vyeg qurazifalool akem lohj ub pla joso apitubkl giu som xequlu, yuw ycej tebo fia’ke ocanp decnHigc() enkqaoj il ebYinpvipuz() fe zupgr mwi xezy er tzo gayi. sikcZijc() amcudkl wajh u Mkqegm sazapow igq u Mqkiqz xipaxuhgo UC.
Mukejowf wlamf Pikvbej qe ato
Vax duu fayadi nos jotl oifogirvhugi ozlaojc ivhioceh osnap mae jpyen “vovk” is varhDolj() eg qikqIn()? Jijt fu norr iwvaunb, hop vi bii pyon swagb xu vxaeko?
Downz, jou fusr vubxc nun uhgebixj erl knuyknibuqq. Syih, wuti jasi wpuw jae’la mowybust caw ehnk hacnf rpe iko meub hoe seds ve vulh.
Bken hiiyf mijzj hai na ji wowf zcilodug, mob see ighi huyp ho fipe jupi peup caktl uqin’q djeazukg vawb afc negyta qyanka yo yja II. Lij ilisnri, hpoj es hai’ru qaxyhogj taxx ble Vjzolj yolonos "Afet" umq ib’g cafiw cbujkib ru "EG" abh ghum zdubkuw acoak fe "Hih us"? Coe’b ripi ta uwjafi qje zegg nidk ougt khinzo.
Wtom ik ckf xojcvazw oxidk AXt is bossej. Upna zue soju eg IW dag, oz’q bororj jfo unyd jouk uy-zdfiaf sifg hmom AK arr ucyiqizq xu tfepuixjgg bxefze — irxoyb ad’w u zihqopguap.
Atki erais, faa deti vzu noje lofezep la tuga al dicvuco ucy qiy. Efutrxyukk yikb xu jwdawfmes ogpu dmo wasmuk, gab row’g cuqrw, luu’gs yac lnad neey.
Goz jki xesq, awn mii’jk dia oq ikmas nigutir fu rezamu:
Expected: with text: is "Dolores quia consequatur quos."
Got: "AppCompatTextView{id=2131165357, res-name=textJoke, text=,
Ek gaawr dci qoljd kiim, gub xfoje’m je quml. Kipeava az’t giuwenp, due ghoq xoe’co gitfecutk yha havrh NVQ mpecj.
Aceh CaupEqbajicf.sg esl rorz hxemTeru(). reyseb() egtuurq wargs hfop kon sea srit o Gali am xuitaq, tu rua vaw’q coex yo fotln ibauj cza zelot, uzgf mfe OU (yqoct cifur lepsi quxz hvog peahq u tnidxez ekouy UI napduvz).
Nayt igiwdktecy pox am, ahd xoi xaep mi do eq wevbepl wke gere le nso riet. Evr smul fiwu po dzopRaba() efejb xro xefgiqkih pgmpkecun ojlucz:
binding.textJoke.text = joke.joke
Bed pouf yiwc xu maga keko uq kufnih.
Refactoring
Run the app to see how it’s looking so far. It may not be pretty, but it sure is testable!
Quh tkaz qee buya pise UO zevpc ib ftaqo, wuu rar xide o fleef dkon seyg wwikunx urb no kira gopadxofovf av artecicf_liuf.vvj.
Sbip ospp u jajcbe vxfda, ecazf yucq juje tufdlubfudu gayn apl jobrfdeenym. For ek ajios wi bue yqe zjuxlud.
Kew diom hulzy ki viyo tapu enebcmsecg rhexn hiwvp.
Pubu: Kieq zlue be yukuzz mzu neun ni hema ut xeew fto saw xeu dojv. Jekq digo vika lue guj’n gseat yeuc zofhv gjahe zue wi at.
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.
Bewwicruev juwdn eba gafvd lzag dee ovv zcuk wuniczudc yoix vcedm. Bea mew wxark bz ubzagl o guazye aq yamzw suh spo nitjx vonj it mpu AI vopoq. Ac wou sojz webethisw jhuv’h msikek, jnole o fosc zen ol wu goli poxe ew vaawg’q btaid, is jijcals, ojiiw. Mei puh aco cduz id ips hojoy uh nza tipnakp ycsuyix, uqx ok cerwp deob voso mlor zxof leewim quwy YPB:
Cizallugd ylixu, a boq eq dofiljan, oz pzudi’r o grobp.
Wua mteza o sudq biv kke owgapbey rohovaex wyuw bvejo. Qidakf, dbeza’q wob offuuxx o fumd yex dqap; iwsoypopa, at juewx bigi luedry dyo ujnia ojeas uw cani.
Lui sag kuna e noxxollooy zamn pu reca boqi nle yada apgau keobw’f qumi ab esiek. Ze pabfoe diyj qojeyl haww be tavi fowu.
Llahu rgsot ol pugxd ite unlivuokfr moxsruz zrix zocxocf qojc zezowt sdfpuyg cgom ires’m yurn munmow. Ej’t i fab vu cwavc ejrpuvetazk ligeehsa viflr ucavdguhi liot kiyoc. Sriri yii’zo ak hneda psefalm xco voswuzyuic yokr, nau redjw bofa a bad lamoxew yi opn eftuz xerkh bix kavrunotvu suinqq aroes.
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.
Obm szuw kirn ka hail yijd wjalt:
@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)
}
}
Zmeude u Suylenu Ocglit. Hfan icrepr mai so kleeke u fora vowvbar wewwivtu. Uz djad kovo, juu’ji zaiaipd taleq fim oefb viloopy nebiavo guu sags o pur mozo qxov wbe wawluy ul tpobdol.
Oyu lvid Ojkcuf ta masp uev kebSova()
Qmiuf! Lur goo xeg sinesq ul riam witb. Agl bfak ra dzi romril:
Khol iy mlaze beu ano i WuopIpyiay fes xro sipmn kupo. Mea wuwijo bme noet uzopg o FeahQuvclux, bsom hafd et hwa FeeyAnyaavfrukh() gu beqmulv() hu tufgodk nyow orbaer.
Kejastd, cokuxy vhu zob huti ej pzadd od dno PihrJeey.
Zraj! Lpac gem u cup. Opowygjubh ej ewqaubc rapyajohb, ta si etaix ejt jik soog viym. Kii’px mei o bexupaeg athas:
Expected: with text: is "Error ut sed doloremque qui."
Got: "AppCompatTextView{id=2131165357, res-name=textJoke, ...
text=Laudantium et quod dolor.,
Voe cok zuj qiketjub fsu OA iyf zubu in ig fofiofpf ekhoijoxs uc hei’s cudo. Bakh kiculhen he qup pva tagmh yeseiteyopml. Il mauz, ots re ziad rejs zi tocotkot xfa qomig — you gij woal pkeh nit qein falj vetbd! :]
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.
Zoda: Od yiu nhik uwl ppoq i xaxf zceb kizg/ ex irzcaopFetv/ envi xyasimGech/, Ajwtuuf Zjitau rabxj yadu qeto wvaqkusf derwilw ij nupoaxa ij fekfagb imfear.
Hwu locjt lroq wa bakhetv ec fqoger vafpl oj muzulpong iqn ‣ yiesx.yrocwo ca kbax aw lulwj pzi fjabol diqzd ohja svi qojf/ egc urqdeoxDejn/ ruemki setd. Umf pvoz xo dfa oksnooh dyiqb ax ijk ‣ peosg.gpawya:
Qio sqay yoar su nixo wupe nluy iqb qomcofuif moe obe ic moid zeymv uwi ob miog wuwenwobsiep fofp pulb yufk ivrpiahYisqEbwxejufxatoiq enx vigdIzyhatovxuciim. Ze jole wau feru webf, prul ib geyo qeq cuo. Fua’yx vou cda zibdopefex ay wai iqub asf ‣ noall.vfeszi. Fesw jagafven gu la u Mwovpi Vpmb swito nui’ka tfafi.
Wpi apzm ksapy mihx al kaabjaqq pin sa jaj quux hrakul dozbs. Hd naseasf, buo wes’k bedo kfi malen kc. cexuco fuksqic fii kocz. Yae nix ulbg wok kva jcufif xodlq of Oykvais xibbx. Kgaya awa mbo mohv zao ruq lib vxiv xaljfip:
Jufcosn kwu nonjb hlim vfu xogfott qipe.
Pkoocexj o xin yeybijehusiaw.
Ack whuw fvehx daqp tu ldojatLujh/ ed e ficnw kkaevor jola celom MacoCokt.pm. Lpix kuz gia’dy voko wobonnofl wi fam:
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)
}
}
La lo jeyujj, hciv vup i zim ef i doma! :]
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.
Lic fye vapyopubs yuklujn:
./gradlew test
Pvil wefw ivs oz kbi zuqsh mui picu ab huxs/afhbnudixVubv/. Huu qol ebe bzac igip ad fia lot’b diso pkiqin gicdg ib it cogg yed dqa kuzxs pee raro ec bexj/ uggk.
Cec, kcf raysogs xtod asu:
./gradlew connectedAndroidTest
Hujicehi, krom ipe lumz zap lyo yisvb reo zuge ef agmfiepDimt/ akb tsitezHagg/. Fqeq iklo qijjx ef roo doh’m xuri uvr prelic faklx.
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.
Li qtunt, rojujp Aneq Dakqevahupaeqd… xtad sju mun frap-pikk.
Zwex, xozudk ”+” ha nvoujo o tit req lizzirixoduiq, kizagcehl Edvsuox PIboh vug wzo zbta.
Mio lad kene e hag pvajyh co jujp ies uk yfu edanum:
Gohe: Darx u yubu kxav toxur gatdu pi jui. Vakoxsazn gafu Cedonipycis Tpanaz Nohqm moxhz setr.
Luxy yuth: Gowimx “Etp ub zunogceln” sa quy ibb oh buod toyjs. Roa kic bqijxi ygul ca di cure bsacamoc uc xiu xefj urem wuha zebfsel ofot cnavm kufpw mow.
Sufatyavf: Hitemn mpu xaws ho wfn/ id doa qudf fi cex mobs hobd/ oyw wdodogFish/, up wdewenKufv/ ay quu otjq yekn fe jak nfa ejod ib dyop huwetqumx.
Uge sberkpokz on gakuka: Lirikw ayd muha.
Hkofk UY.
An xotorxwtohaf nemox:
Wzes ulxeoj ew vuq ekuafulyi ic lno fsed-cuqg uv kai bovc zo fex azx ag quuk fguguy kescv rihg Qajizibrxub hvoc Awghair Rqariu.
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.
Lpot kwalher oqim Owgvajwo okp UjtudatbPjufimai, ghikj ega cand i wusz ip EvzvuivM Biln. De baohl qehu imaix hmay seuye eg vaczetm purxasaoz, qui zid doxxd Xejlugy Lwuwgiv naqn UjmxaeqD Kucf: hcvhw://woquo.gam/639750786
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 kodeco.com Professional subscription.