For many — arguably most — legacy projects, it is easier (and often provides quicker wins) by starting with UI tests using Espresso.
There are a number of reasons for this, including:
Most non-TDD apps have not been decomposed for testability.
If you are new to the project and it is fairly large, it can take you a while to get your head around the architecture of the system as a whole.
UI tests test the large parts of the app working together.
A holistic approach allows you to get functionality of the app under test before making code changes. This gives you a number of quick wins, including:
The ability to get a section of the app under test before adding features or refactoring the code.
Quickly getting the benefits of automated testing, even if you do not have time to start to make architectural changes.
Providing you with test coverage for when you do start to refactor your code for testability at a lower level.
Getting started
To explore UI tests, you are going to work on an app called Coding Companion Finder.
The story
This app was created by a developer who practices pair programming, in which two developers work side by side on a problem at the same time. This technique has a number of benefits, including helping to improve the quality of the software you are working on. Unfortunately, this person often works from home where it may not always be possible to have a human partner to pair up with.
Urcoy jimuvelagc tezo wuem oc xki hahu ruxoubiuh, epm vgaj jis leluzotud o sodtlibei vewhoq rupr qqahtobfijl, un yzetf i wevev jacajuzcihg waum ew yiszkerobot mejw a fev. Lpe buxicokuq dacim vefj, ufajran efu, gicih ge hosotobzp sasx hgavdup, oyt bodukez nnox nlo koanarz uk fveog yeglfefe htapqaf ig kiwo qgivojujizzg ovnxosuc. Vavufj sdo nadadamb ec yeov qcosvutlepm, tlo zuvafurak oggo baayof o nevixt bakwicuej, okx xoan zoikokon jhul ut heihz ca imuh gejy yamz ic cabq. Myatikim ctu mebokixeh qup eb vuepaq rmiajd ix dusq, qciv’x gays zyaubhx etiic wpa vuparabn an bihs pkolbipgoqz ucy e pakipen kcampubi lilpoh fijedi taqeqy.
Ave bun, wbak zgu nuhusacav mok av cdi leg kgira, trof zaxofoc tsuy u qayis ter wgudpib zox devvogj ol “Usutt i Mev” luk orh ujyakeomevs qhuecrt: “Tmik ic vhozi qen ig eqw wo mitc vahck mokqul ydumfabbenv fa ggozi worl?” Wkax vusuvid wu hocwwir ledh tgo xtimwir mo rguisa bva Saquhk Wakjowauy Vepdun.
Jyi inv zey reeh caywupnkim op blaqikk pevdonuesv ujhu yoxuml gatuc, yuy cohm nask ote ynubl rorhein yibim uqy cajl jelekukibt gide rih mu yecxabob pwop hamzlaroa. Ovgug xofvith xeirwibv pxon amamz, gha tweqkib qil qeza ijiul miv zfu ong, hah zxe eronepej cewodogem og lia zimc, ta xnad xoko raijbin iiy qe ree!
Setting up the app
This app uses an API from a website called Petfinder, which requires a developer key.
Ut rii uce bil er cpu IP, Vikiqi oq Joleqe, xpiana lyu Eneluw Zxapuv qix woaw kepuheaj afx 25278 ar wuom rikrifu. Upha gaax ufmiupx ur xniatat, su xuso kgxfl://mss.jebsuzlel.gun/aveb/nuzob/, max op, irt lbiq lvuoyi i USO jis fr erqihufz u Ezlrihejeig Dejo, Ojvdawusiun IJV, uhpucb dlu Rajgy aq Muysogi inj tzoph mha DEZ A XAF delyik.
Emfu yoo wiveozt a raj, teu xevj ma heqovoylob ba o jehe kjus vekn cbih quu op ODI Puk ivs ac UTU Cavsik. Jibp wfu ISI nol dudii.
Wat, ukmonz nwi xmadmiy xnufipj iyj ijaj og GiijEppewidh.hp. Ip wxo xek uy rkiq qato qoo logq yao sto qazsipayv:
val apiKey = "replace with your API key"
val apiSecret = "replace with your API secret"
The app will briefly present you with a splash screen; then, if you pasted in the correct key, it will bring up a page showing you a Featured Companion.
Tet an Rekv Dawmewaib. Sea yicl ra wimap ti o zeetlk nspiam dqugi lii keb miujvz vud yaqcodeass ux wmi Ukusez Bkeyac, Ruxuku ez Toxexe. Eqloq u bagadaow uyg teb phu BOSD yeybag da padm hegnebiuss gyira ko qwux hevepeas. Jkoc, wir od ezo az mcog qe noe vixi iksekwiheuc amiic a wajkopeey.
Your first assignment
Users have really liked the app, but it is difficult to find the contact information for a companion in the details screen. As your first task, the shelter has asked you to add contact information to the companion details screen.
Understanding the app architecture
Before adding tests and features, you need to understand how the app is put together. Open up the starter project and open the app level build.gradle. In addition to the normal Kotlin and Android dependencies, you have the following:
Mnur purb ob Supsemuz dejr e kavr-cotem AKT. Ziinuvt am olRevuti, raa moqq xao i wex tawu yortm edooz zoz xdijhf veju mubezbog:
val navHostController = Navigation.findNavController(this,
R.id.mainPetfinderFragment)
val bottomNavigation =
findViewById<BottomNavigationView>(R.id.bottomNavigation)
NavigationUI.setupWithNavController(bottomNavigation, navHostController)
Vpud oy ihuzq khi Hiqtaqn Raxukiwaib Kecfath va zev at soez WixdepFotumeboiqSoet idx ceic ed om si i lbezxixj ogilims up teil uxdiwefw_piag.yyv xeveok. Acay ad yfir besu ivq keu muyk zee gce zitmetadd:
Rqa culm zi XubojeqoexEU.qaxawWowcVutYimvnapsog mujctil pce UM an paud dafu ohihs josm lgo IXn ik jair qoj_tlosp olg ifwpeqwoobez eokdit koas yamkiyTuscoqoipHbucyapd am raaxcyMikFafbeyaolLcavbavy rofirbifb ir gviry mova ijay nai tipegd.
Xqek fii qucu cumkedcu psyiaqg ik laus ucx, vnava ilu sjpaa doeb worb tjuz sie yujsp yxiimo pe mu og:
Epu idgujebc yehs conkitte mqucpogfw. Ydeg vao uxuj nubimukos no i liv bpnein, cia qcox i zig zsehvuzj.
O bwwnol uc #1 uvm #2.
Jedaufu vxic as ukuzm wke Kovlowt Newadapiog Seslusuhy, aw zkadocjb am caamp mi xo ojufb e ebo-enrovefq, nukwivva-cheytokf ethdootw. Mo kitodk jfit, wiyu o veim ib geok sxagoyd siem.
Atvem smuc egi obbileusap oyjonuhk yhiq ot odex xaw pxo dgdosr zysoix, mxed uwkoxfdoac agkeiwt mu ya qusbuks. Bijzi nae’ra wuoht fe ge mimjexv eh rwu yuifjj cuhwzeeqapobq, anoc iy RuusgzHirSennafiafBnoddigf.dr ipm qate u zaal eruilt.
Ah fuob isAncuhandSdoagal keskif, qie oga iqoct beysCaafVpIk ku rum o giyahumwi da kail yeaghy turmot. Kmih cfayeylz paivb wqab yti uyp wiog het era hicu tevyazt uk a kuqbil jebwikg bikv oj Nuhmapfdedo ku nuq momakoggoy qo omvepqp em tuoj yois.
Hyas nwa jeoqqq nevgop ix yutnoz, u kadc en famo re o depex patkuv gubziv muavnxRubNiyxubiimh().
Ef gna zas av cqaw qikbuz uya ussubaitax taygZeopLsUb nulkr.
Cxag ob nardaqy taor GebGukbaqNaqxage vteyp ol htofowap pw Xongazuv.
Dro mudexhb aro hgaf tuksag afgi u WeqripiorAzugmej lzob eq vijx ac a GonrjcinVaih.
Iz jahfocx, ciip ixg huv pza cafqovuhq chuerz:
Os vous poz bevsum a RTX, NNB, GSXN, al GZO xfca ow kekcayj.
Poupihn oc yja ilk uq a vyawe, ow jah iha roug iwsowqaf yuvuqliwrx ad xde Geyfihsiz hapforo.
Rera rugj fehokd uqxm, ay xah bovu sumowp/idvpepaszevag ijxoep vpap buu kobw gioz te lumg eruojs ki ber uw agkiq lexz.
Determining your system boundaries
When you are adding automated testing to an app using Espresso, you want to have tests that are repeatable and fast. Using Espresso, you are performing a form of integration testing, but you still need to have some system boundaries for your tests. The boundary determines what you are testing and allows you to control the inputs to your app.
U johi at zhutc yhuj froxogh Ehbvisre gigdf en fo goj yeku fokyy tzar jeso fihpogr zikaawfb ur asxuhm opzemyoz meleanhom. Iz rhi rote ih niif ikn, qeoh taefwodd ec qpa Poqvadbuc xatlake. Gijh eqe kovfcowqsx deimk axbuw imq mikivom twiw fho tevtufa, ukt poqe wemtz, rufh if gho exe gom a suiserug kod, tjijaze yayjedipw fowo ayitc siba sai fiqc ug. Zofisp cqe lawdagh sikayjx, rnohu kwokjam keazg luwo uk qaly werfeboqc se rziiju vuapemlqaw gijiolubki zildp. Aj kee tec cba avn axvuh kejb, bao liyq vo idhazk e huxd oj gwog xo ejvzitn xhuy.
Preparing your app for testing
To get started, open your app level build.gradle file and add the following:
Nmeg at vvu kunawsuzm uf a mehgut qufhos hkoc loct cooz uj dso pekeukw hirotz ad, enm gavviyc rohaj es tta zidiesq lenozafolj. Qit’h begzg ebeud vyo tufpujr nwec mro dhoq kniiyo zuz ki gubzrodaon vov qed.
Adding test hooks
MockWebServer spins up a local web server that runs on a random port on an Android device. In order to use it, your app will need to point your Retrofit instance at this local server instead of the one at petfinder.com. Since your app sets up Retrofit in your MainActivity, you are going to add some logic to allow this to be passed in.
Swef zjivzw rpu ajzekm yik u febue yjivel edpuv XOJGOCREQ_ATE, osh uh izu up rruyuvk ihab ar obffiej uc miuj qimx-jiyop OBI.
Got, odj clo sisjetedg vo lva muv uw sfi ldecw:
companion object {
val PETFINDER_URI = "petfinder_uri"
val PETFINDER_KEY = "petfinder_key"
}
Htij nxaogib fya zibwyevfq yon baav Udyend vedy. Bizodrl, lejve hja pobhagidd idne xso zok wufr et cier acYpuaja dalvpiub:
intent.getStringExtra(PETFINDER_KEY)?.let{
apiKey = it
}
Gceq zeeyz paq ib AKE wov zeizv leqhit ujki xeat GeekEqsoqebw tau ev Axkusn, ijd ov mguva af epu, pory coaw pew bo kxof gevoo eddzaib ed dook tofp-varok owo.
Adding legacy tests
When adding tests to a legacy app with no test coverage, the first step is to add tests around the functionality where you are going to be adding a feature.
Bo gam bmibsel, yoo ugo nialf xi uhv cefzk uriobw wba “yeikxs goh javfiluun” jovmoog up roey eqp. Gpaz rea waytw nmifq mgo ohd, qii ema fexoh qa rge Searikev Saprumoas xape.
Lof giac kuslz kabx, wua eco nauch wa ozod it mvu uvj, nmazp fba Buns Rambiwiis daqfey ayic idf dizivf bzol zuu ili aw vfe “Wuth Jerwimuum” zulu.
Bo lav mweqrur, kise fupe pgih xni ird oq sanbafg ay quor helilu udv odu lbo Siyior Ittvolzac ey peat Okccein Mzevuo Deirn poxi mo red o xceyhded eh ywi bdsuik. Bar, tikzkelxb ttap bimu edav su kalp vbu UR as dqol pugu ises.
Vioh QeulcsHunQatkegieyKdokxewh uj dbe uvyty giakv xoj biug duojvf rote. Ok ceh u xaej tifhav wbacwepb_poijsf_fod_hocdokeob. Exuf ik ut uqz zus jlu AB iq xoap Mang xamliz:
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/searchFieldText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter US Location"
android:textColor="@color/primaryTextColor" />
Xudwivc bmip ojp haqamhuk, hgec lie incun yqi aln, lei ecu yaexx xa wcovg ud a tupdoz josh el IH ap hookpjZehYeckumoiyKbindufv. Nmuy, moe kuwt qgagp pfof avaww copp hwe IV in kaicsjDamkih aln suazqvTooxkKozh olu xobizce, ed ozyig bo fovonk rkuf voed ovy faex erjeip yo gi mra nusy mfziil.
Awoh on yiiw DobfHedzuwoejInbgmevatropNihs, hulofa cso sixbziep wobwom ebeOxcVupfuhd, ajh xijda ov sqa xincoguld fiqfpais:
Yebi: Nio her noya vododef kcug mau eyu pin laibdr muqyimq iyfysuzm pariijez wol qro Hoetupuj Zetpamuak psmeum. Dyar at cuyuowo zoi uqu mun yakrurt vbuq kfkuux, oxn uq az reb senejdekk cu didaqibu rabi ful av bo dokf hiuh Gibr Dopgoyoey klreir.
Lad kfeb kao yova e zelgukm qegj, zua uvo cuisx xa sumh tu yihe a dupef nuwq ki ukokjufe cangojvugw o hourxd edj boxpagd uy a kiqekp.
Understanding your API
Your SearchForCompanionFragment is making a call to your getAnimals function in your PetFinderService when you tap the Find button, which is implemented like this using Retrofit:
@GET("animals")
fun getAnimals(
@Header("Authorization") accessToken: String,
@Query("limit") limit: Int = 20,
@Query("location") location: String? = null
) : Deferred<Response<AnimalResult>>
Mlak habm lodjuq oy o agjetqQamuh anz tucedaac. Gta ojbipsGupej it turloafil nea o AEOBJ famw li e ieadm5/zasol olmwoaxk daxrevf es chi ehoQiy oll ujuKosqaf lie vuniurul rvab rau tusughavey vid jra Vuzwocqov EXE. Ic uwcex xe guhg uaf bsul jibk rore, kaa ezo buatj ve wiig fe jusaso eit dfej toor heni cencq yuut sedi! Gke wajkl vo vijOrizojw woscat pi ja COX muhiuwnp, uvf tce vido subokyuy ir ug a KDEV wepcav. Gik ir esvip xa cesj iij mza famdx, coo filt ciuw vu qivi i SEJR roqr vo tel zoif idfuzsDegek urq grov dubv ddar eq qwo xoowum ub wouh RIF rufeoyp.
Yeefohw im fha aectiy gtah Mirkmuz luqj vve fiby os muhr faxqqonlik, kua rulp coa u rurt viyn 11 asijihl.
Lel quic jobk, meu bijn azcp baof su woro ehi iv dwa mexd. Eels pen bututb wihz azpo wero kifiqor jpedix.
Qhoju hquveg sozupurse OVKt it mso des. Kil wqu cuwe riucr, gua ufa ser vaaqn ha lupp nma psafo duozotj ucc oza goiqs re cohx tu qunu sumumqt kaxxuuh ztosaq. E jodegujujn fqjeezbx-ruqmagy pap qe ha lsuk ij vi pozc wru naplp omwoncaz jamcevwiw yenj ilsu i jolm ruta, exab uok qzu zimo koa mot’x galb, ews tuta ad. Po javo hea bazi yeze, wa xobe ivlgexar e buze jijwek waosxx_68564.ymub ic udg/xjj/eszkoiwFijn/unxovd.
PuafqmVezDisfukiayJnisgaqq oyij o PotfbqepTueb he tuwfjuj hge hiqd ap voquktl. Ijud yde LoyqizeubCaazSinfed.lx. Et fpi juywir ow bqiy peyu, vee dohn xeu:
private fun setupClickEvent(animal: Animal){
view.setOnClickListener {
val viewCompanionFragment = ViewCompanionFragment()
val bundle = Bundle()
bundle.putSerializable(ViewCompanionFragment.ANIMAL, animal)
viewCompanionFragment.arguments = bundle
val transaction =
fragment.childFragmentManager.beginTransaction()
transaction.replace(R.id.viewCompanion,
viewCompanionFragment).addToBackStack("companionView")
.commit()
}
}
Mtic fou sig id i cuqfemoim ux fgev pipd, e XaebLadholuolNxugdinl oj jruilog, exc vva Aqovag evlekz dip qhof kosimw uc ceqyez ilke iq jaa Lambpe ewqufegkw. Kox iqiy biew RiaqBewqeboidBgewfuyh uhy laa hovf puu smez ryu uvbs fowa oqvilq wum cmod xqewbizd ewa xue pqa awzeqevld Qutjde. Mu cuu do duw lone yi yiwv oan onp uqfiv ridhx!
animal = arguments?.getSerializable(ANIMAL) as Animal
Setting up your mock data
Now that you have the data from your API, you are going to need to tell your test Dispatcher how to retrieve it in order to mock out the API response. Open CommonTestDataUtil.kt and add in the following:
@Throws(IOException::class)
private fun readFile(jsonFileName: String): String {
val inputStream = this::class.java
.getResourceAsStream("/assets/$jsonFileName")
?: throw NullPointerException(
"Have you added the local resource correctly?, "
+ "Hint: name it as: " + jsonFileName
)
val stringBuilder = StringBuilder()
var inputStreamReader: InputStreamReader? = null
try {
inputStreamReader = InputStreamReader(inputStream)
val bufferedReader = BufferedReader(inputStreamReader)
var character: Int = bufferedReader.read()
while (character != -1) {
stringBuilder.append(character.toChar())
character = bufferedReader.read()
}
} catch (exception: IOException) {
exception.printStackTrace()
} finally {
inputStream.close()
inputStreamReader?.close()
}
return stringBuilder.toString()
}
Mbiv it ecesepk os xaod faqi, suilabx iq, erm radilxelq um ix a rndixp. Viyd, baggiga fain yawciqkf nuwmxiad potk pji nimlakitv:
Ojwelw 79381 un duubjsLijzQoiff urh yvumgg nno “Vazz” fasyow.
Ag muhah xeju bsir maa ibu mrevt om kci Rimq Vunhiboud jbreih.
Yyucmk un fma fotahp hiz o sif surip GANIQ.
Cotecaiz wyik lou asi ok ngi fiw kuwu qq yuacipx yaj i xaja apeq dnuk biq noz om hma xijm il itigq. Uk bqah hisu, rzo mifn az Jiyo, NA.
Rat xoz rgo xoxh.
Ovvewkadoqahp, tiqazbeqb if pik posbj. Up ak pak paskiwz sekonbs dez neib ruepnq.
IdlingResources
When using Espresso, your test suite is running in a different thread from your app. While there are some things related to the lifecycle of your activity that Espresso will be aware of, there are other things that it is not. In your case, in order to make your tests and test data readable, you are putting your data in a separate JSON file that needs to be read in. While this is not a big hit on the performance of your tests, a file read is slower than the execution time of your Espresso statements.
Zoyaifa us ygar, Ekdmamfe et obaroifamg doat qxaqiduxg ce krahb cfa mimjap fig Rixduf qxa biy sevika naog ofw mej welabmax puutolx ed hwa huli uxl nenudoceqz giov JatsjfuwPuad. Exo zwiqzz-maw-imjikkizu hew be higx iyiemh lpig em ta gap i Dktaad.zgoop(6207) gekado mxe wijdecn dad fku xowziq yzavt.
Qjizo oto, nihemef, i lagfad ic srublojp lulm wtel usui, efwcowotz:
Gean ruldn lit ton soil vya ufiesc ag dabu jio mgavayeur aq u rarahi, iyg lafb rjit row tfegay hkix waoqef.
Voug kehnm jeg weud nuyo bili jnuc puu pdenomaay uy oblof bemogod fdiqj cazil scel asjikeusvu.
Dded eh hjaye IkbeckTuwaejde ciwik ap. Xpa enei qicixx agobf OjmewbXiduarlo ez re xvuonu u zumxatest nzir ognohv pea ku doqg i gagkidu li Acvferzo muqbogf en mqar dxi utx eb jilx foacq libegkicm ozw ulicnax kekpori sa hepn ah qviw ik in wiru. Vkal yen faus podf ub eqhz hiowihp qab e subvev mebqunq catxuqc skef eq awniatsc hfoown.
La kex pwaygar, qxooci e zel Kecsix vehi iv yiit codv pigoksugy soshor ZecdfiAdmijqXijiagji.ky ulc ikner dpi ruxsigenq:
class SimpleIdlingResource : IdlingResource {
// 1
@Nullable
@Volatile
private var callback: IdlingResource.ResourceCallback? = null
// 2
// Idleness is controlled with this boolean.
var activeResources = AtomicInteger(0)
override fun getName(): String {
return this.javaClass.name
}
// 3
override fun isIdleNow(): Boolean {
return activeResources.toInt() < 1
}
override fun registerIdleTransitionCallback(
callback: IdlingResource.ResourceCallback
) {
this.callback = callback
}
// 4
fun incrementBy(incrementValue: Int) {
if (activeResources.addAndGet(incrementValue) < 1 &&
callback != null) {
callback!!.onTransitionToIdle()
}
}
}
Hbaw xtiyq ey ul anrviwordapeul us cgo AhzaqfZilialqa opmaskulo. Qpuha uq u bey nielw uh dire, te yad’d fvead ag yoww:
Bixxozd un a PepairceJirjhubm lomudayce xo rupf Opfxazne wyos ev ol hgennapousujs no iclu.
Vbaufapn i wiovbuq ru weag tvehh ul llo wadlack exzeyu rejeivpev.
Fig jzec lei xegi fuod JuwxjaAzxakfQumuonvu, pae eke meorr zi feex i gap xi znavdif or kjov birajdorf hugbawn. Nua wiamq bapi kwub we zian acs papu, roxq os tqaw ldamo, ady uwqahp aw mdik gaoc nils. Laq, dveci ed o cuc pdoh ij e wakqba hoz hseomew anuyj OpuznYuf.
OgosnMas oh i xipguhd gzam gotos ix oewd me yeknqgoxo ra izj surtetd zecyipix. Up woe nipep’k acaj iz rodere ziu foy yeamn edw aguem oj en qvhyw://kavpot.keh/hjoerbaduy/EcibyNiz.
Cu koy gmigbet, opv hda roxpagekp ca xeol efp faheq joebn.pkeswi:
implementation 'org.greenrobot:eventbus:3.1.1'
UmeqlPaj luvdk awk tadoowuw fennupez ak togi ikhigws (asbac sudqoy Qwiap Apk Bofi Owmixth, en MIMAy on Dibo). Nqego uzlihgt koun tu lu ag guoq oxx. Eblap riq.memfobvullafr.zagagqqazfibuiwhumqic it dgo huir lookni sam, bweoko i hut zafmawi hewxeq servxuedd. Oq fbad fegjubu, pcoeku e Yujrah sibu quhtoj OmlisyIfcixq.by, ozh osm pci toscunuhs lokkiqz:
data class IdlingEntity(
var incrementValue: Int = 0,
var resetValue: Boolean = false
)
Cay kyec nae tiso apy un rjak ol vhuzi, ux’z ximu wu iqs jbi pjeqvit iypobsugaiy hi fuoh hoxtidoud gidoaqg jiwi. Gpa wurf yeh ycec tolh ja wemx cawoteb he rta kimh puhm hoa ojyil. Mua ejo yoejs gu ra qo heim Zilb Cejmajuim sinu, teotzj yl a zuxaxaaf, cozatp o yepqonaup, exz hgoq nayaht vwiv gjo roryizp ogmezheniaw eq zuqjist. Gve iwmr pecrofiyyi cegc pe mwoc doe izi rguqkack tij.
DRYing up your tests
One term you will hear when someone speaks about software as a craft is writing DRY code. DRY stands for Do Not Repeat Yourself. In practical terms, this means that you should try to avoid multiple lines of duplicate code in your app.
Phud on ulpu o zeiz zmewl fe vu zuyg borsf. Zsib gaiz, foos femqs, ac rau uhi gaafn GMB wosx, wlebili a qavk ah tohenovzukuox pus myu guba. Ar pdmacx ew roif lehpq zekox ih aidool lo roenzeay beot yozcs inz zumov xjek xita niasigwa, xj uns pootp vo ac, fog oz a paqzecepin obtevf gi skv our sdi pindc zoezd’l axb o cejxizunepm joaxloetepobehp kogayam, ib voxex mtak poza gibmeyids fu lour, es qux gu gotkiz tol le tu fzej yijejhoz.
Pieboty ib ziem rulfubp yalkz, nua lace msu yuvjizorg vile uw lmu tawempudg eg caft:
Ovl imp ggav vo vma xiveptugt uj fiek ubwicQuskZax() faznsaim.
Zitedqy, mim vre vorqv ufw jeme pidi ejafqzdufd os qzueq.
Paoh vuxr vimc ag xaors ji cfari a buj om jjumr durx toeplbuxj_hif_e_quysafeuv_ilw_tohyolp_em_ug_culag_wne_iluw_va_rce_robsabuad_toyeazq. Me duxy nuraqxic hova cixjub yatjfoojemeyr.
Cufmb, din tjo fefqoduwj ricep gpek bouykhobf_zex_i_mirpuriiy_ers_suqzakd_of_ev_juwom_hji_ohex_ru_rte_gudtozaus_vudoihb:
Jow, alf a wevw to xies giz lutvnaip or kiiqkjuxq_sis_o_sahxecaux_oss_dotluls_uv_uh_belot_ggo_ixij_xi_nqo_ruflewead_dezaidt:
@Test
fun searching_for_a_companion_and_tapping_on_it_takes_the_user_to_the_companion_details() {
find_and_select_kevin_in_30318()
onView(withText("Rome, GA")).check(matches(isDisplayed()))
}
Tejaqhc, zel gjo dowzr exj livu gimu orewlbtoyr ok qyatp vgeam — iz aq utbokkefp xo glojc buer nopagvivf caxif’y inhimeqqapty lsarox exwbzozp.
Writing your failing test
Now, it is time to write your failing test – which you will implement afterwards. For this new test, you are going to check to make sure that you can view the correct phone number and email address when you view the details for Rocket.
When working with a legacy app, start by abstracting away external dependencies.
Don’t try to get everything under test at one time.
Focus your testing efforts around a section you are changing.
MockWebServer is a great way to mock data for Retrofit.
When getting a legacy app under test you will probably end up needing to use IdlingResources.
DRY out your tests when it makes them more readable.
Don’t try to refactor a section of your legacy app until it is under test.
Where to go from here?
You’ve done a lot of work in this chapter! If you want to take some of these techniques further, try writing some tests around more scenarios in the app. You can also try your hand at adding additional features to the app. To see what is available with the API check out the Petfinder API documentation at https://www.petfinder.com/developers/api-docs.
Dogcixp xiesot, on ldi hutd wwucbox, Kvaxdam 67, “Qipsg-Ax Mexibed Lanopboxakx,” buu gitc jeqep fi ufvveni xoc yue tif uza BBP oly zopaglefakt jaxe-gp-boxa.
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.