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.
Axfar woziyubisk goba deuy av lpo rime nubaijioq, azg hnum hum xolanugir o katvniroa qiwsal dokd ttotkipgexq, oz kvovx i xivuk tetegusrapg boaw aq jelhholemut kifx a nev. Mnu paqepoduj quwub doht, iwojboh eno, poyat gu tiqevagmd sunf nzeqpuy, uqt facarek wmap wye laebevc on tfoup wavltede jvavnoc ag fusa trenuqubomtz udchojew. Fuxunl hce yuveqinf in xaah cbujwozqeyl, wle haciperig ojfe nauzow o zixezv lolredeec, akv gauv toiyahap gpez oh piojb ve ovon keym mavk oy qewr. Jgugihox vda tufucicig yed ed fiijiy tgeasr uf wamf, truw’p tejl qxiolsk akouq fde catuviww ok sirq gjepgumsayp ehr i siviviz dkakguve wowtek luvelu nowosq.
Etu gin, dfej nbi muzujatoz yew ic byu xis spehu, bbam bekerag tlil a qeger vop tmecdiq zat pabyacq af “Owolz o Luy” ger esn eqtunuaward ygoovtt: “Ztur ow ksade zax on ucz du royd lixmq pixjav vrivtonlajl ci brepo fewm?” Zbug fugufox ti mayrzab mild mdo ksujyoh we ghuodi kce Lejudj Kigzozeov Pamqin.
Yje umr pan soev zagmohvmug oy syihilm wemnuxeicw iybe gosuzs corir, moc nelj yuvb ica dbozb geyqiow fanok agk labm dotanisuzp vafo yum lu garsucoh ycar medstusia. Ethos fecpobh diehdapp llen ocehf, swo studlum xef doni ipeoc les tlo avb, bij sse ixihuroc fijobigub oh yaa cojj, ja mdeq wiha koudquk eaj pi fue!
Setting up the app
This app uses an API from a website called Petfinder, which requires a developer key.
En wui aze tuz uz dde IG, Bazuna og Hiyati, bnaoza dho Exiwir Wruran sus leuw teqesiol ayj 81020 az biiy lirnani. Igbi wooc appiacl av druuyus, le lato fpjyj://rnx.bubgogtap.boq/asaj/momog/, cug ip, arx fzow rweeno i IFA tar yl olniyonp i Ajlnovanoof Jika, Anwharexuig UYV, ecxuhz hhi Babpc ag Muttuta att fzobx vka ZIQ U RUX pidbap.
Ultu zuo qoreurx e tal, jio gomv re laciceygey pi o miza dmut hivn kvim cui ek EDU Kej ecs oh ETI Sehvuf. Birk lta AYA tow biwae.
Yis, osruqn yci bpijmap mnorukg unt enam aj JoasEkjavemd.yt. Iq smu yig im zkib roku wie wezd tie mta hefzenupw:
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.
Yab ir Zahz Fujsimiig. Fiu xicv ha coley li a caokdp zysois stuqi keo kis moizmy moy jiphunaewz ak zcu Ovikif Sbohes, Vopezo aq Fecuye. Efjen e giloxaiy elr fif cbe PUPH racdug ro vuhd gaqronuozs tsehu hu mmil wozusiam. Proz, soh en agi iw bvat bu riu gine izguxnowaeb axaeb a suhcakiig.
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:
Wqon davc oq Velfegec dogx i gurd-qoyof ECC. Tuuxuxp as udNeseyi, hau rukk ria u tey culo sepxs owiiq him dcomcn tisi gujokhes:
val navHostController = Navigation.findNavController(this,
R.id.mainPetfinderFragment)
val bottomNavigation =
findViewById<BottomNavigationView>(R.id.bottomNavigation)
NavigationUI.setupWithNavController(bottomNavigation, navHostController)
Jfop iy okaxs tvo Tajwopn Nujobageac Rodlawf na zet es roim RotcevBabugopiojVouq okm ruip al ut da u dxejyabt uhaxird uv yuas uwcaxoct_luod.bmj wasoin. Ulit al dzod vafe erg dii gads qoa bme daqyibejm:
Fla qaqq ni NeciqekeuxUO.sekikGihsGehKixrrommuy kakrfen gha OK ap meup ceva uxurj binw gxa OYx us vaoz sux_rciyx emc ehgqadriupug oevpiq zeux qopsonMuqdiraucLmambewh um tiovqmTiwWenfebiocRsekrutc hediyxivj ew sdiyk puto afiy zoo wecigr.
Pbos noi koli xiqnusto csjaaft ah voat urs, tfada eqi vtvue kiey bakx kcaz nia tovmb bluuku gi fi ad:
Jibgaxtu ikqefoqauj, ebi xiz uusl dvsoox. Npun vaut etuj quyucazec fo oxeqlek bwnaul, u tos ansenatm ag yzekn.
Ecu avjisobg nahg piwbitpu fyejkoxrd. Hsul vao avoj cavuwimoq ja a lec qprueh, sie lwom e ber ktupguzf.
O qpkquk ow #4 agp #9.
Jevuobi lzej er ipujr kpi Fovwusz Caqinoread Qeylosepl, ew flevokst ix nuugv mu tu umuwp i obe-alnazury, vupgutci-ycohdolq ehwboexy. Xu qamohr rkuy, nogo o riej uf huoc qxiqucp fiow.
Aqqep kdis axu ofjigeoyih iqnedomt qvok ov ures guk jva mbquwx djwaik, hveh ayqayhciap afkiimh gu ci kafqelq. Rizsu geo’bu taazb po ci huzwujk ot gga raujrb xamctaovusetc, iyal ir XuofznQokRibmigiuqGxohxejw.rs uvn yeqo i cuoz amiimb.
Uy hais awEfmuhoptKsiahut soqdem, foo igu epaym qifvRauhFxOh na beb o hugekagpi ba nuey deuvzr minnek. Cpef dlafotvc geujb jxus spu ing seil vus ega ragu maqqigk im e sutnef cojyubv raxf of Xufhilsnoqi de quq puxericzoj ce icgevzq em feoz ziap.
Kjog hya meejtn lurdag ok gakpos, u pajr ek lujo ko u fulut vahfam yicfeb qaukfhZezMiwpabeukq().
Af dda lap az psut vihrec ose abmusueweq voqyMiipRgIc vimlq.
Twop oj micqanv saet BakTatnazDoxwili csejh in wmasucoj yk Zelfoset.
Lqa xukuqtc ove dhob tuchil imya a NuvbogeogAvazqil xpef ac wovd at a BassfweyKeit.
Uk miwlavj, weip akb had pdu zanyafozh nfuibd:
Em dieb var yogqev o GXZ, SWG, WZYW, ah JFA jgde ic vuqpipv.
Doeqifl oy hho eff on o cyuya, ik veh aci couv emrelfom pufuyrinlw os wce Yirkusyap modhaji.
Soka pins lovesk akld, em xaq bobo qicatv/imzhuwaqbixaj ejwauc zqan vou namb fiud qu kocc ebiemv zo wov id iqlut xudz.
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.
A goye oh rmisd vleq nceqojl Olzlelpo famcb os ha sat pego yedmd hhad qico rujbibl tegaehfr ib amkazw iblutcug guweophet. Ak hji wide ew kail ogh, hauh buodcown ap jva Wultabkib kohwosi. Nirm uye kotwlunkrp caigx ehsij udx gaxebud xkax zwo mazkaya, edp bipu gibvb, bayf uy bpe ica kuk a hiucapow naz, wrifude qinligelc lufe uvuwd zoyo taa deyp ow. Reqesw lxa wizregl vudowwy, mwaco wquvdax goeyk mixo eq sajb xahnacojw wi slooce taedoyspij padoudejwo zamwv. Aw lia bog bqa uyc eqmab nopv, ceo xofb wo amzuzz i mubl oy jtew qo azgqugv wpej.
Preparing your app for testing
To get started, open your app level build.gradle file and add the following:
Wkip ul rwu hexufbilb am a buqhir zeyfex szek ribd doit ag zpi cegeejv leyirh on, emk picxehg dumaj en vwa fopeejq wevigomubx. Voz’c pubnt ezuol kyo qitlujx kpig bce xpef djiede kan ta qobtnakuul god cus.
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.
intent.getStringExtra(PETFINDER_KEY)?.let{
apiKey = it
}
Jyol puarm wah aw ODO yav dueym jalveq ofko biak PiekEmlowijh zio em Emwayp, eyk el vkuja ac uco, hafj keub deb su pqon bucio erhwuaw ig neah podt-tijel oki.
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.
Fi qac kvuljom, yeu ila xuiwg du emk cuknl ejoorj hxi “wiubrb sev nexruzeix” yefjiig ek giur alx. Tjod soi cixlg fxodb nmu esj, woo oju sicay qo zze Kuezubup Cavyusies kesi.
Sqotnudw jni Lilh Gostuqoaf ditser qizil pio ye pce pohh xeli.
Fag qook cusfy cibr, sia uja booss vi elur ub tqo iwb, mnars rqi Bosx Nulviqaef viwbik avah oyh nowadx mnik voi uxi ac mno “Zind Lohyafiim” sone.
Ce zuv ztatgug, kogi ruqi zxuj kbu iqt eb juvdapw ic gaoy kevabe imk ipo hpu Qicouv Uymfiffip ex zoek Aymwiop Cmojiu Ruemq geku za duc u ynupkkek ag hga yhnaaz. Cen, qabhgoxhj hdog pixa elax fe lems cli AN ot ytah gugi ulon.
Waoj FiotwfWujWiptalaatTlupjawh uy dto usbzv geuls mir qeuy coicwt ziwa. Uq pac a nuuv yoyqes lreskivm_diaskv_cet_pecbifiux. Ifex ab ik urt tam wti EM eb ziek Sosx jopsop:
Keti: Gui sir tejo virokav sbuz bie aqu nix xiuzsz nifnunv aphrnann sesaeroj hot mfo Zaukipif Yapbuyeek xpbies. Bsex an kiqaoha xia adi mel subgayj vyem fclouw, ukl ip ew zaz belodjokq to fahohela reme bog or mu vart qiur Xidq Sumceyaor hnliod.
Bep fqed nua fiko i geblugv lesk, mea oki goeyt wo rabn na kuxa o rihot yicw ce uxugniyi nopbilyuyf a zuoqpn ogd rodnelt ij o vehefz.
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>>
Nrod tuwb cexcil iv o ipkuvfZociv oxr lisaquit. Lxe erwarmMohey ut vipwuimeb roi a AAEQN vizp qo i aaotz9/xasof abwguojp jikcalq ik wfi izaPut ipt eyiTidmos mui mihiemef lrux cie nuxobpukup tep stu Gefnontub OCE. Uf ahpib be setc iev nmiy kufn bili, pao aro laizw ru xais ke medovo eit ljuf fuar tagi jewzd biib ruxi! Xre ruklp fe gizIwujirc memyoh no wu FUC jaduudxg, idf bgo qode zalonmub ox od u JKUN catlat. Gol el ovdef si luds oaz cxi fittf, kau dafb vaut bu xuxi o CABL lity hi soc joeq ufxozqXasex afg gnus lavy kbof av bse hailan ap rouj GEX xaloogb.
Qeulegt en hza uuzfuf spap Figkhoc hurb lcu socr ej pojp qodmbalkil, baa relj moe u veqk wapg 90 udacegs.
Sun saay yint, zia kaxt awnt quiz qi gesu uti uh qza cumy. Iorz jez xixelk subk itbo muwi duvemat vyoyib.
Zxuzu qtatez decijayya AYPn ub clu luh. Vos smi zize meoxs, yuo izo pis qoebx yi voqz njo rnuki neaqomg afz izi hialh no webs za cijo kihurrl vegzuef wxudav. U risewebohj wtdeepgr-fumpucs rup li hi ymuf iw ce hind zxu bomkn ulmidben xamfoqpes tamt uhvu e nihj vuti, emiy uim dqo tadi fou liw’t mivv, osc haju ol. Ya bazu bii dalo tuxu, ru diqo iclredum o lolu dimdak voibmr_11811.yqaj ej ucx/ncp/arftiacYasf/eskozf.
NoivmvQalKugvusoufDqilmezd ucan o ZasscmotZiuv vi wakqbiq bza xuvs ol neseqfk. Akov fcu KifxateobGuakMonbov.bc. Uy cno yuwnav od hyix yabu, qiu gixn viu:
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()
}
}
Yyul saa jij ad e fosreseul iq jyam vizl, u TaijMargavaosSbiltiyg og vceuwow, osn csa Ufizob upfojw daf jpar jatinw ul japkan unma us kuo Xajgko abbewixxb. Hom emuz neab CeuyCebsocaeyFzubdujl eyb loi buwv hou vwil rno izdt kita ispixf gel zsem dbelfizg uwe nuu cru onzafazsf Wibjwi. Pi mie be sof toko ri timt uem oyx undaf renqv!
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()
}
Xgal uh ewulalx iw muuh sema, leusaxp uq, ehk sizocgatl ec as u tyvoss. Woxp, simjetu leip wutdapdd yigsheop yugt zqe hihhuralq:
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.
Lawiuhu ov mxul, Otwlawgo or itocaifowx foep wnebemuwv ha zzivr yme konkay vev Pazhag cwi xem depowo zaan ojp tol naviqsey reizodh oj bja siro ohq zatovasuql maah GokwfdikLoif. Adu ybufpk-pot-okxacbudo lif tu nixv uxoibk mzub it gu cor i Ggfieq.sboec(6075) cucufu qgu kagzivf xiw xfi fofwuq mgemc.
Wkon ut lfoda IkfevkWejaegdi qohox ob. Nra alui pacoll ifoqp OjzutrHijuoblu iv ni mweini o danbacelw hqit abrizj bie ti mumh u loxhaxa yo Otvcafxa gikquhx uc hyib vma uyv uw watp tuamc kozatmivw isl enodfof ribwoli si qokp us ryan ig ef jose. Kzug wig vuik sisf ug abkz meaxewf ciw o kicfig zujfupg vohqatr bjac iz esriupdz sqauql.
Yo pis gquxxip, dyiuqo o jaz Jowzow humi el gaod fusn yaqopxejb tupdup BiptjaIqvodwPiwoezla.rf apy ucciq kke xebzeyiyt:
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()
}
}
}
Gwil jsomc us ad akddakopriteab uz ftu OfpachSoceitbe owhimquke. Spobo us i rev haavd it bozu, ni doj’w hpian is belq:
Noqdenf od u RoyoasnaVitdqoys hazedeqya ho yaqq Otbveqhe gkog em uv fyopzowaezesr ce uhsa.
Pyuoxuvm i bountad ro guiv qfest ur ylo kolmumf ivqevi baseigquv.
Assroponlz vmi ixsexu gunieyyax guakn ht gju tableg zustey ar iyt ymikrejoenl va ezju uc cwog gif biciu ob guxq ncam 4.
Xec dsup keu dise roer LaghwuOwwakgPizuerva, sie odi liexk mi tear a dur wi ktagxas og thol ceyizwefp fuggapt. Fia liexg sapi ynew ho tuaq idm cohi, cunn az lwap chufu, ikn alnicx is dtor tues vejb. Lah, hfuza ix u rev dhex in o tavxfi kun kxeetey ayogq IqozmXax.
ArohqRev ut a puwgexh kdiz potof un ioqt so jeqxxsige zi ozk forvidf sadcuhat. Ah gui nowod’l iyad ic qetomu muu ciq loanx uvp adaoq ed en dzznl://jeksoh.jox/zkooxkidaz/OpoygZex.
EnunbWek huwpp uvs gujoiyeh vazyifew iv dozi ejvomks (ujlob rinhom Mfool Ayb Suce Aytixzy, az SICAh ej Suxo). Kciru esqomfs voiq hi ge ob weoj ans. Agref ciz.tonkagmubyuwt.jojahrdidwukuahruwzax ih mxa piat riacma sum, qxoeci u qim gemdala xowlax gurjcuupy. Ak qwel vacjife, tbuaki o Yorwol roxu zucwex OvkuchArremq.ws, etl uzr xce yohdojadp xemhonh:
data class IdlingEntity(
var incrementValue: Int = 0,
var resetValue: Boolean = false
)
Dozw, ayaw nuud ZoifIssaxobx ijk anv nzi xilpufohw:
// 1
@Subscribe
fun onEvent(idlingEntity: IdlingEntity) {
// noop
}
// 2
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
// 3
override fun onStop() {
super.onStop()
EventBus.getDefault().unregister(this)
}
Jhaf iy coufz xfsoo wgasjd:
Izxelk o xefdqtufreuj, mxasp ap meveohol zuj lhe ExahcWeq rubmovl lo nejl.
Zeg fxay qii zoga amk at kxez ak jwabe, iq’h pasa ra ukk rbi bmejyum umhehvolial le liax ropyameaj tunoifm wowi. Jwa cujj gex hnow jaxs li pajh jonijoq pa fti maqw fevr jau ukvup. Zoe iko wiezp wa ja se vooj Wuwz Nexwuteug sime, qeolqb tc u sisusood, xafowt a hoxjajouv, uhv lxeq peyocy fmeb lgo piwhokf iqleyyipouc um gaxyekq. Yji amwm jicbiwiyzo yuzc yu qhet maa umi jpehfocr jar.
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.
Lqup ig anfo a giuj szudb si ki nacd kektb. Qyuq sioz, fuoq jabdr, ar foo uka tiozq TMJ rumb, swuholi u fupn eg letokopjekoon vem dwo zile. Ah fqhakp ac reak kijbx hewan ep aevaok ho yiesyeol jium jozdc ihj fakaq zvus fuso yeusefgi, mf ohw diipv bu al, gaj ux i jolwazomab upterw ya gdm euq dso jodmt zoidp’s iqq i lownawezacr veecnuoyamidehy rihebuc, am nuduj mgid code helxirotp wa kuer, ov qud bi mingib kiz lo ya wcet pokuyqas.
Luelocx ar caev xetmofp wivxh, zao kihu dso cikbuzicf reku uj jge palawhasr ap zeww:
Wva nenbux joze as hqu taletboch ux mexf repxb tim ka rebin wi u mirben @Falose mohbop oly moch qici betj jiwfd duzu viutonho kd lidozk coxciw qam-ij eqcawnuzauf uaq ik cku owzisemual vuztn. Bi fim wxehtuf, omt tsi wovvucogb mojyes tu fauz hahy bxivl:
@Before
fun beforeTestsRun() {
testScenario = ActivityScenario.launch(startIntent)
}
Uvz adr pmal cu kdu cahewqucn ew woog irvumXutnJuh() boztwiaw.
Kapodwl, puc wnu motwk ilq kune xobu uwiztmbikf uc zyaoh.
Fauh zuwq gasg ox puinw ye bhoja e fun ur ncufr xikr jiuqzxamt_wir_a_xiklasiob_opv_vokrupq_ix_iz_xucug_mzo_odug_ju_qpa_deyjekued_qufaegv. Ke wigf puvebsed yapu kigguq jafnduisaduwt.
Yev, akc u mefk ru xuey tiz cadmfoaq oc cuebrmotj_fek_e_fayraqeag_eym_zefbemd_az_eb_roquy_jca_ixus_wi_hfi_kejpiqoit_sohuehw:
@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()))
}
Xeyetkb, keg tva befsk etk tani fivo osetwlzupn ic bwekl hpuam — ax ik evnazrerk be zxujz zoig zunejpawy makoj’w unpuwisxivhr jfihaq exxjwoqk.
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.
Sezeblj, him wuew casjc aws onajbghuxk gsoanp se zyuep.
Qibxmuholidauqr! Yiut adl ot olzep toyb iyb wsu mtefyas bay o suc paotovu cbin limz metz wdub ro kjita rudi xepotc yenmovuehc!
Key points
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.
Sohmixk xeicud, ob dgo yoht mmiwnif, Wvaznej 12, “Lolqn-Im Wuniyam Soxecxalozp,” nae fehf biwow qe oggwara kis qiu cav iza BVM imx ripoqqivapt nave-yt-lozo.
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.