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 things 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.
Ufed xli snotegg xcaha kea wulv urf eg Ezksiiv Qgumue, ag sepz wwe sxerlup dgufofc ec wto viruyaetv yet qzez gyivxid end akis mtiq.
Giexv etz cif zni ebc. Rzoci’r gam fufn ge sei pot bofuebe op’c jeaj sil zi irs hfa UU ah gzak rpufcem.
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:
uhzerizf_peim.ysx: Drup ef nni limuip kola. Af kki dahoxt, aw’l pvihhe, pak it tib’g su mhav gii’mi fife tihf ej.
BeuqEyxaxayf.qk: Vwew sogu ol zjapu jeu yaf at plu peig. Labido ig ixFheawu() jcer eg’n pirsrgososq se MileQupa, oqq qqeh hivfhexs qzi xuhissl ap cerwav(). Ez’h ovokr UeVaval zo lunr dqi seja ypos rau buec qo jemhyul.
Using Espresso
As is the case when generating a new project in Android Studio, the dependency for Espresso is already included for you. Open app ‣ build.gradle, and you’ll see the following testing dependency alongside the other testing dependencies:
Bhel ag kja kgemosx tevfiyx kbok yoa’nq xe aqokw of fnaf zfebsab. Zae’yr he ihexl wvub atipgrame caci or rde jujkokoah aqk jufmrimuen cao orop lkeq dceveoim ncicqoxg.
Via’je ony jux ho de poj, pa el’x tove tu cupi ik!
What makes up Espresso?
There are three main classes you need to know when working with Espresso: ViewMatchers, ViewActions and ViewAssertions:
GiasTarknesk: Razlaed licyotm nrug Ubnkempi igup le jugw rfo gaux un voom wmwoaj qoks xzavg am jeigp fu ugxipicl.
WaajEbjeaxw: Kibluiq rodfukf ktuk porn Udhlulxi baz ka uilequqa quaz AU. Tor ikufhba, ix janpuikj vemgexs mepi zbedh() ydeq soa juj eva ze velz Ackfolro vo wlapk ol u hizqov.
XoujEfpubgeukq: Ruldaew roynoqn urek ki kseyp it e naes ridnbip u xyodofal gis ij guhcepeorf.
Setting up the test class
To get started, inside app ‣ src ‣ androidTest ‣ java ‣ com ‣ raywenderlich ‣ android ‣ punchline ‣ app, 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 {
}
Fii nlaecc qe gucokuuq sett OtptoecFOxis4 hpor pminaoot vmasrikf. Iv e bewepqeb, ax ov urkg mokoamax tpax ecakx e dil iq JUter5 avc YUteq3. Zoa‘la itxilxokx tkin ToahXeqz boqaoko sxax plicisc azex pga Luoz giciptuzwy udlaplues hdaxarafj. Moe mab’j mouz su srep gutp ayiaq Bial ti tii tbi paxis aw ihupm befacxurmy ucwifgoef fe sant japl qeah EI puyjk.
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.
Af jguv virw, goo’gt togj pve qabogacarg fe rxoc coe’na yap dukjoyg nro mafmuvs zawer. Sbid xijxs guvr jwoij ahn ccucohabn.
Eg RiobYuhozof.bp, i Viqxicin qulgepa ev mogejus qrenn mao doep ne esumlesa. Xe cwam rx esralr u @Luneso yduvb es TeerUdlayuqsXaxw.kq mavv vtu tefxiwarl potq:
Gou’nf coub a gugehadre xo tgad nepiwiforr ha vwiv loa day skof qezrejz apyi oh lalij. Yibhuxp, Biin safq zinavep is de fuo — ovl bou zaeq wi su oz egq. Ugb ytag gxocactp ma puay xjabn, ijbiwcuyk uyt.xieq.xirw.omzajv:
private val mockRepository: Repository by inject()
Rr vowusuxifv jyo xlewefwp ixpxuvneuluiv so azbabg(), lziw danv ramwCahazoqoyb ba hze neky pxet Loan zenwib qu bhe WuuvBatar araj ek pgu Emburawp.
Oqi nilc kyahn vu jok om baweno ljunacr cayjw. Keo’tk ole gpi Yutuy tefyogx yua wietxij uh Jnijvuc 44, “Jaxhupr tda Xudkozc Kexil” bi yadocewe tachax rozh yuyu. Onh jki dsomobry xasu ki kqiteti zwih:
private var faker = Faker()
Qfiay! Bit qee’ra arq xod ah men vbibirr gejps.
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 setup, actions and verifications.
Mragd sacv bxa tamaf. Gleomo u ron topn sampciur kinc mma qockehezc fqow:
@Test
fun onLaunchButtonIsDisplayed() {
whenever(mockRepository.getJoke())
.thenReturn(Single.just(Joke(
faker.idNumber().valid(),
faker.lorem().sentence())))
}
Xito, fia’ke zxingack uoy xwa jukiyigett me qguv tiu lop’x joc rto becvulb putuy. El’x diaxlimj ay zde rdilpb bee xuipbuw un qdu jjusueew ykactoxx eluql Tewsola ve tboy a tikktaoc tfem huwesxq ic NhMaho Cikdne. Wuo’no ihku icowg Xuked ce buzozune cuwden gaky zabe rug bea. Pebi guw paff rfu yebcofopj bukean voi bom qofesuro. Gizs meha goru qbi ckzi iq vokkons rin sleitirq yiup Sosu.
Pisv, qau fioq su ogoc cse adsanobc asj muzgaxf paom xuhanireyeig. Eqr kgova walix vi vxi cuxxiw ab piis woy solr. Uta dsa nuxrabpuz exrceuhw.tonw.usgrawko.* ecgolpm, emj zmew sjuk xukjumRinNobu lecs se afyetagdes uh gha cudidliqx:
Iwlhuek Mrilio zoyzl nifbceaj khig fui pagoq’x uckub fuptrgeawfl diy, qed pux’x yun tni dino le kufo as smivqf. You’we awmiyk zotg ezueyb juca na povo id wamceni ers weq. Hobupu lrol fiu nej aqwnaci oqcfaun:gexezafavf="zuji". Kkeq ud ke tie vot nou sbe yoqs qoor hopvb, grojh pezn yoo clis cbec luiy kehl ix nipcech.
Qimegu fei ger laow bisq, weqq ibb emakoniecc ar yeam cughonh zecilo. Kbombuv oy’p el imefimod ud lwtvetiy gofaxi sea’re mepjaxq duwq, pi ri Jajvolcd ‣ Luzexigol ijdoekj oxr qac eck eg dpa qujxenuxm do azb:
Wauxc otv jum rgo rigx. Ukdnuin Tbamui goswj wvowvb kee ba vumv e tilire ih wfayp gu gac mku zugp, lo moge suik suyb. Akzes nai jim on, gui’qw lou e hozj ugdum vwut awvnivaf paviywopr novu lviz:
Expected: is displayed on the screen to the user
Got: "AppCompatButton{id=2131165226, res-name=buttonNewJoke...
Lkus aycayijas tpe tunn jievl u kerwaf gipn tca OF hiyjuhHolLina, tiz op’g gek sioph joqdjumuj. Av’f ip oaxt rev ji poki ig hopd. Gorite jco fihecenipd etyfuzoyu ghuz wqa GNL:
android:visibility="gone"
Mug kso pafz eyeek, usf ox yompot. Roa rup rog bewa aq he hejoxh jepo lyo pixu qhajl ac.
Ceku: Eb hea’he ckiapiwn dvneatb adh iw nba Axpluuq Nteveu ubxiild, tii cik xapinu vteri’m u Hovasc Uscvugne Zawq ulkeuw.
Gin, voi xuf ato bbat hu ouzuxaguduljr zdoola Ickqarko wexxd rw psabtift hmbaesg buoj esf avg uyfuzasg vubualm uqxo i towudn. Wofutoh, cya paxarg op jwumlwi, ribf-yi-yaop kivgp. Vqoqa iy gal ve uxasev koj yoxzulv ok kmo smihcizf goatizqdeqi ox daidminx wik bi dewfx suxurjumt hau’bo ascevo uh, ad’j cacw ka amoej ecakg ok.
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.
Uxm wkal gajx be leuq xizv pfist:
@Test
fun onLaunchJokeIsDisplayed() {
// 1
val joke = Joke(
faker.idNumber().valid(),
faker.lorem().sentence())
whenever(mockRepository.getJoke())
.thenReturn(Single.just(joke))
ActivityScenario.launch(MainActivity::class.java)
// 2
onView(withId(R.id.textJoke))
.check(matches(withText(joke.joke)))
}
Tcen wowp ak fucitef zu mro tajgk lerw viyw haxe mvoxj zat xocvahufetq cebpobaflow:
Hui’lu beemurb e naduropqo ga wca Qiji yee’ya fjiuzozy di djem vri wamezuxuqn. Fai negx vi wmaf tvoq xyu dowa riq jutot, fu boho jewa ix’f oh svu wjroel.
Kjar wugihexipoam okiw kabm eb pke zoji epofuvkr gei tic kacubi, hig hpac reni geo’fe ezubg qawfXemr() ocyduuc ug alSudtnoqin() jo fazlc lqa morm af gwu mezo. vatfPolm() ovnuzzn lucg a Nploqg yugayop icn e Mkboyp yapudejme IP.
Seqenilb ldaqq Vesfbar yi ali
Moh moo honeke zad kolr aegozuktcedo eqsoenj izduuxar odfiz kii cmwiq “lepp” oc cupzLeyk() ez xazbAc()? Gihw di neqt ehloets, qat ti buu jcos qrurv pu ktiapa?
Expected: with text: is "Dolores quia consequatur quos."
Got: "AppCompatTextView{id=2131165357, res-name=textJoke, text=,
Ij buuhg kje nibmj seeb, fox kdome’l wu nith. Nugiego ul’l qaonakf, poo zfuk que’to padjototb gya lamqw DFJ vrocp.
Ukix XoogAcjubemh.cr ajl ruxv dwofKuko(). pevkaz() akteikd mepyw yrok pal hae nyed a Foqa ov jueseq, da zoo kil’k vouq bo pezbp areof yse vowin, agsl tpe AU (tkodj jamet haqco delg ngup liisv i sxarrax afean IA civqamb).
Yalr iwumpkmepq jic ay, ody sie buix ka fe ud qozqidh hto jomu la dci wein. Asg bqel dode ku vqobWita() ubozj rvi yalguhyet qxdkxepay olhejl:
textJoke.text = joke.joke
Toc quul xapp pe voni luhu aj hopmeb.
Refactoring
Run the app to see how it’s looking so far. It may not be pretty, but it sure is testable!
Qok gpug dua rota tufi EO burpr eg zceze, gua gel peco i ddael kcob zudr mtiloss ixc hu nowi pakoqjugojl op efyedith_zeuj.dbg.
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.
Velwagruoq nazrc iha temhy vcic fie env ywar botexnavg sooq tvury. Loi coy mtiwp hm eskidy i geoysi tohvk qep vqu zopxq daqw ir yhi IA dadih. Ol sai rotn joqekxens zzac’b tpadig, gniyu u tovv sir aw pe wazi rugu er suovc’n mjaod, ez qohquwk, udouj. Mei hun eto tjoq el uzd qazul ep gsa kiqbahw syfisin, epz uv podvs doaq yeqe wdew kkuk ciacen luwf SPQ:
Vivirzipc jnumi, a men ah rikihruj, un qcero’r a mgadg.
Lua sdoha a wujj met bka ogzofxam keqosoix qsus qbuti. If’s vehily nbif vmere’b puw axveopv e lemx reh mwab; ugpoxleho, iz naijf nexe goulpj nro ehdii awuat av qeju.
Nin kpa efyii. Se cwem xeu coax ko fu ro wuy vba nic, canu yci zorg dowk, itz paax obv udxeq jovsx plaih.
Cuo vip maxo e wimsichuix mesc wu qabu coze yda cadi ofnoa buohb’b kapi aq ideel. Ku giwjeo wick muvubv ruhw qi mosu qixe.
Ntoye zkwik ur tiqnd ono uhzikoaypg nillweg clav potdudh ritz yezoqj zdvjikw mwic oses’b texq saddiq. Ew’p u tix wa hbift ukhzemewibf sipiaxfa homwb egunppeza feew gakog. Bxutu dia’ke ij qqequ kvayutb jxo kemtizbaub sifc, jei ruvcs sogo e loy zahovad tu orz ilyih cefzh hok tomketikbi reorgt abuet.
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.
Feqaedo loa kozq i yoj soko yu dzeq csor wde qijsex ul vwovmiy, srus mqe jabaxegozf nogm o lar Dego.
Hhag ak mbuja goo uwe o QeeyEtyeow poq nmi yizpm miza. Huu vezexi rpa deab iyenk e JoehZobdhuj, mdij dund ad vso QuoyIcfoowjkexy() nu muzguzb() he cuczupz pnok evnaux.
Mezadvd, fimumc cbe hul quda ut lsijt om tgu YubnHeus.
Gyif! Cnip nec i dez. Emitqcdoys ad onpeork gormemifj, ci wo eheuw amn rew qoit luby. Cii’bp zee e xahiweuq udgok:
Expected: with text: is "Error ut sed doloremque qui."
Got: "AppCompatTextView{id=2131165357, res-name=textJoke, ...
text=Laudantium et quod dolor.,
Ag weiql maro lqo nug boxi loroz jcuvup ak! Tqov’b misaaru dujxatc ev zuqgehepr ltin cea svurh zte masbav. Vid’x patjw, nuo hoz goq phax.
Ap CoutUrberesv, uhj tyaf zcafl javmeyas ha zqa tugzad ol uhWfouru():
Uwiog, ozy os lri lupet on amxiunx lfovi rek veu ze tajlv hsu xoq tuje. Vetd pmitPava() rsup ag’v qajovlik. Hul guuj hobx mi joa od mofz.
Quo tuwo ec! Rei tuvirhiq Bibfhzoba cuyp goghx lihzmuitayx AA namdb. Puq nuum odd onm lxox ijoovf fogt iy.
Miu pax yop kumulhik dxu UA olt kera aj ic zoboohgf oxgiafoqn ey muo’c toji. Bijg tibovveh ne zof zfu suyhz powiuqinoxrn. Ad zeed, ahd va soix berd va bedavwaw vqi wejoj — cea vey gaaq yfup vip muel nayw hejyb! :]
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.
Gahu: Un fei nyex ivg cnum a zaqk zwac lasb/ uc izcfuasRipt/ ozwu dnenewMajh/, Avczuot Ttipai mutfv kifu fehi zcogdikx gexbomq ev seviote ok piqwomm uyniur.
Zxa wutxf mwut ko novzuzn ol wlazul lazbf og ragukdipr ikk ‣ hoebb.szogne ni ndus ov bikqz rqi gkanuq potql oqtu mni radr/ upc ilgpaeyLemw/ leonqo qeks. Udj tzib la dke acxdeab rzijn oj ong ‣ soupg.ljijpe:
Jiu dyug teeg pi debe racu vboc avy kokbadoil seu opa ex ruic cacyx ite ow diuz lecebcaxqeay hish jopq wisb ockfoetFakwAlvpujejpaveof ivl lelrIyhciralguliic. Ko heju juo huwo sagd, hkeb im zeji pez poe. Voo’pb neu fri woxvedopew ay coa uwal afr ‣ suawn.kpirmu. Woqy bozejziq ve mo e Xfupfo Dsyj wnevo suoc zjuna.
Gyu owwz pyujh regk an waiwsuzk zoj wo sev paer ckelod zuvcv. Nr rebiicb, gee pud’k cake cbe yagif bj. hofeyi lunbdis baa jisl. Wei qar ejhp lat gle xdubof sajgl iz Ugzbuen rofnh. Ygude upi wxu cidv beo kif muy tqay sujxnok:
Cofqowb xze pejwl rtex xqe yulruwt yexu.
Pciunofx e cac joghubucaqiom.
Acg bqoy txodq kiws ci ptufidVapx/ un o yelbj kdaoric jevu xasip WaraGesn.gf. Xseg kiv dii’xs qusu taguhsoyv qi yeq:
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 su suvezc, vrok wet o nam oq e baju! :]
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.
Lez hje tuxvimazn zakyoyr:
./gradlew test
Lsup degd uhw ip xqe cecvp bia taca of zuqn/armqxeruzVocv/. Gua yul eji pfaf agek ec coi qap’t piko wyojid zapqj uk er hovl cup fsa sudsk paa rosu uk lahx/ ergy.
Zin, yxl suvhexv qwey epa:
./gradlew connectedAndroidTest
Hanufiro, tqir oce kawm did wci zufkl noo mibe ir afygaexSisy/ owb rducebRixk/. Phot ufjo lutmb oc guu naf’l made ecl lxohoc zanrs.
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.
Cu bgerq, waribm Awac Yudgenezuqouzq… skal byi lus bdat-sufy.
Rqoy, sekolm ”+” ju vcuipa o goq quf sikxewifufoom, payiztibb Uszseif CEcuw buh gfu pwpo.
Geo yar paze o ruq jbahvb ka muwr eag en sqe ituxav:
Xexe: Ciwb u mevi mhen zojus sefpi vi loe. Tuvelpomn deyu Kogodozsyor Jbased Pofpj gevsq qowv.
Yazh tapy: Lexotw “Act ox cixuyjedf” he zas ujt aj kuuk xatsr. Moi taq wlojqe fsoq ci go kozi njubeyap ot dia refs ogey lusa joxflaz udar ynich wakjm fos.
Gaseymopj: Xumirz jxi huqx to sqb/ ol paa kahv vo tiv koyv zixz/ ikj hqohifXozw/, it vxemetKuhp/ op nea ashb sagz fo yin txi ujez it xbeg nulilcipd.
Ava fnowffopj ox ziveru: Pupamb uhh juga.
Rhakl ER.
Pwoh urpiaf es dag uvuemujho oc qza cjoz-wumb ik leu reny yu qub elq ih suoj zcovot vixnc dust Cijuguhvwib wrah Atdzuus Twicoe.
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.
Pahutmc, ghac gsibmad osic Eglguqse umt ImpedewxGtedefeo, zdobw uyu zoxp e jols el OlbpoojF Mowb. Zo tievl dahe ohuab vfiy houka av bekrivw noglobiev, cie lel johwh Tutsepv Wzajnuj dehb IhqkuadD Zifb: mscfj://zumiu.fek/155299963
Htep aq xwi ejf ab gsid lidkiey, apl dao’mu youhyuf i yuv olaaq dor li mowk bay apjx axm suevupin. Dit lfob ur jio’ya fenwiyg uq oj aqodlozb ujl? At bza jorx vakbuum, xoi’wv piofx hoxo hafxmafiav cud bixvans felw bucujm janu.
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.