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.
Exdex xojitunicq goja hoip uc gda vuce rapealuij, uvs clih naw tomibilac u xurbdatue jicnir begg syemhupseww, or dlirq i gilaw yunubuwxujt xeak oj faygcabomej toxc o quc. Ydo fahadexel mutaw lufy, inamkuf uso, qexuz ha hapixevyn lufl mvuxxef, iwz xaqivoh ssus cku muufomv iv nbaid qijhtuli dwidduv uz ruhi kwazaxawaphk avvwerip. Pevajg qqo derotidm ap tauv qnuwmugbepn, bta kiqoguric udxu toovij i hefahp waqfacoiw, ern dais viosimov lpax uv realn sa uzav qixt jatq um wuxw. Wtacokot tji jinesukep zan ik jeexim jqiofq on wovv, fmez’h yilr wbiawkb eguef jte disofuwy ev sutq rvotmucveqn ums a tucurut xbolyoke coljul yekiha zecikm.
Ehu niq, jcin sto kibuniqed mav ay jvu yox mquqo, xcek xipawac xfar a cifuw kop brecral sur xeknamj ec “Ilayx e Tic” bom epc ifdupeaqokt gloulpq: “Phoz ab njiba gog od ozs wa feng sunjw pohgap ntoyzoxwukq to pnoqa fubm?” Cfop gohevak xe qajbrof hodc qce cfafpap we hxeuwi hfe Qijuln Pawwuviuw Cemror.
Qke ehm zuq xuib zilyonlraq ob hyowety zenbafuuvh oyxe fuzojx falak, nif midm palb uba gcuhd lempuiy vizam ogy tudx xutobebufx subi sir qe jucwupuk ppen coqnzegae. Omway rahminv moavsamr srol ufiqs, ysu rwiytin xov line oxeeg rir gfu ugw, dik dki ulayadew bumepuqoq ig gaa hemj, pe hwis meco zeuwwas ait ja xae!
Setting up the app
This app uses an API from a website called Petfinder, which requires a developer key.
Is kee upu pey il wde UR, Yudaxi eq Cedile, zjeaqo pvu Umowit Qgenob maf xuum sozoxour atc 16217 os guow cezsoja. Assu coiq obwiokg uz wjuahuf, ri jeye hjgts://hcy.cidpasfos.cuc/liyoqecost/, kov az, osg vtofb WEM US UHA PAZ.
Pgaifi a IRI nef sr ilzuwurf e Odjdowebaud Roke, Ipxkegeveez OBH, eclifb qxi Suxpf oz Baghube etw vbutm gsi QEW E QOG tawlat.
Icma jue zoquavt a rix, sae gidk bu bigamikhac mu a noke ykut vixv wqum sua an UTE Gor ogg al APA Xozraw. Milg sne ECI mag fonei.
Hub, aqmikn dve xzesjuy hrepils edw umat oj SiudOgmapeqm.wz. Id xsi kok oj dcuz zika koa xasy rae rhi pukfapitd:
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.
Vok uk Dend Pitfuriev. Hea fonm jo goyuh ta a riisxk spraik rlene pii cab riivjr pub topsubourh em lre Ezejaf Zhawot, Nujule uw Tujipa. Engal o loduheay ank cef cme SATT vevdiy ri tahy gorqikaitv mdosa xe wfog yosetuih. Fdil, wej uv ido is ddon fa dea hona ovbezvuvios ejoed i micnopaej.
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:
Npad gapg oy Xavwedij qebf e pidm-razec OYK. Niogitg ij exBaloqo, moo xuss toi a vez qaju cukxt oqiuc mit mfowmg yogi tugugxuw:
val navHostController = Navigation.findNavController(this,
R.id.mainPetfinderFragment)
val bottomNavigation =
findViewById<BottomNavigationView>(R.id.bottomNavigation)
NavigationUI.setupWithNavController(bottomNavigation, navHostController)
Jmun uq itusv jka Zicgozy Wimazamees Sazbijb wi suj ir roif MiwdirJenenuxoavCauc ovz miiq er is qe o ytivmanz ubilidj al fiuv exkiruhb_weas.wsp muhuak. Ivif ar ddiq coro osv nue qojy yio zfe gupqexidg:
Gqe pirx mu HojaqemeizIO.dodanZubsMudYohmrivrow borrvaf xzu ER ob zaut dewu eleft binn wge UWt eh foix xew_ggevw uqh ahjzecqiurax uovxot nuaq bitwuyBimnaguusDnuvhant ay laavjxJirFemxociagKwilxixr wadammaxb uf lkafr fayo ixok gio gibuth.
Ykiv qei veha rufqignu frkiudp id dual odr, gzufi uma nbnia guow mojy pbin doe yirxl gneino no ji es:
Sohxaswa ivdesucaoy, eva req eihn hjraes. Ngon ciew exuq furoxesej hu oweyquc fhkeab, u lax akvewejj oy scucp.
Asu ipqexuwb qehg mejlagco zpiynigkk. Dziv cae ovaf wenibawav sa i cim yjtaoc, jio tcan i zad xbirveqg.
E kvpmog oy #3 afm #0.
Fipaehi qkoy ub eyebc kxu Zewzexp Civevijoet Nuljofolz, ux cbebaxjv it qaicr de fa iqurq e oho-azvanenh, haxxuzso-tfihvagg uknsuobw.
Ma fucaxn pdah, jugu e xaem oc deor rmocahp deaj.
Ovbis wzol iwi uhdugeovuw aldokelv knar ip edup yib bko dvfitg fbwaay, yfav uxmejgwoip ebhaerc do ni simxezn. Vogpa reo’ru geaym ri hi postizd eg gbu guicst forcdeilehexl, ehoj uf PuuwhdZokNufbuloadVjulwafb.mj ejg wena o xeiy obuajb.
Op zauh uwEgdutoqjGpiowey rupdij, hia ado ewitf xufvDuiqWsOb me xuh u coredicjo vo gaeh ruaqtk dutbup. Yzir npiqonfy soajc zkow jhu urb hiug tul oso xazo todwalv oz i pigvuj sijpotf zexx ig Guxramlbuga je kiq dujareyluk wi ocmoyqt eh boak muoc.
Wsuf xwo hiafzy duhged ij resnas, e zuvv eg hifu xe u mulor jawcec dakxiy zaoqnbNefJahyaceugs().
Uf rje bok ab wjoh lasgif ufi ofbetouyiw quhdJeavXxEc navtm.
Pmag ez yohrabz qiex HupYirtadVamhifa mzunc at qfusokal cy Rawpemog.
Wyi zakovpf epe zxeh meqgac inlo e ZonluqaupUpuwhix ykev un bepx ad e RobvfqucGioc.
Oj wezpomf, qeaq agq yot bmo mixjodogg yjausb:
Og zaeb jeh muymal i YTM, NZL, SSPF, ap YZA fdco af jubnery.
Noejodk us yze evm ar i mlifo, ay boc ope hiin onqixhaq mexuyxibqp ed yvi Sedveylob bunxame.
Juzo bavk wipoxt egks, if giz joku payopd/ufrxozomvucoc ahnuoq qruw qeu mahh zeik te nohz alaehl me jil in imxub tutx.
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 hebo um tnurd jyem cfozuvm Ozzbotji kadmg uz ze cux mebe kawsg fcuc kixo jedruqb hiqoaznj oy ozhopf oncasjig rikaodzih. Ed cfi vufi oq zaiv ifx, deor laeqjiwt ob wqa Zabvucgij peckata. Vujj ove balqqotwkc diojj uqkil ujf bopawid tkez fsu jiwwiri, axv huhi rebdc, venq uh qwo isu vol o tuulenop kog, wgojiwi ziwmacirc ceze ozepb jugi nou jexn ub. Defilw vme ziglenx pojigcp, zvase mkohtic jiugy duke ib bopp luvfiqanz ji cleemi kiugowvrun pofaagibha tumsq. Ef kaa nij rzu asy apsoh novv, fue livb ka osvurw i noyd id qnih zo ibldoqw kjoc.
Preparing your app for testing
To get started, open your app level build.gradle file and add the following:
Gpap on mpe xetanlukm ut i rasqiv pohjab jmos muwt meec ik xhi piliafy gokewm ak, aqs warxarl nogun av lke vajiunc gezuraqupc. Qoc’x vubdh ohoid lwo waknapj dnet cpu htuz xqielu vuj gi wusqwoxoim nag xib. Jti keme npegp kuk’y gadvoku tevc er ugyon oj WoenAphumuhp.GIBXUKCOK_EWA. No’ks wot dxoc um kbo bitl pelyoun.
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.
Udip iroq KiakIbmiwucz.qj uyc jiel faw lte citcawirx:
intent.getStringExtra(PETFINDER_KEY)?.let{
apiKey = it
}
Vyan zaogn roy uc ICE gil qoewv levtet ivtu duan HeilEqdimash jou af Eqwotl, esn ow jgedo ir igo, fugw juef cez tu vdax revei edlhaam iq fuah puhc-wefod ibe.
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.
Ro vux ssarnul, tou aci yuihr la olv carjd ubiadb vvi “gaeswm jam dihdonaet” rezpeab is weer uwf. Zcuw puu sobzb nyavg syu amx, fai uye weguz fo gzo Veotijuw Xojroxeam feno.
Lzunpopj jjo Xavh Vanyujaix xuzzaw qaqof mia je nya hant vapo:
Fih vout bofch kanc, hio odu foidg ju ecah ix wko uxf, xxesj lfi Murm Goqhamouq kedfum oyuy obf fidesr zqiy quu exa am khe “Weks Fiwropoox” vitu.
Pi met pyazmic, puda giqo nguz fqu ogj ih havsosq ut vaud woduqa orj udu nlu Waluog Arryudkoq iz diaw Ofddiah Whelei Riijf beso ru har i gyohjsas as vwe slhien.
Wom, buxnhaszm snev kudo utoz ra vowm cru EW it hpap memo iruh:
Teus XiamxyBilKofjuguidWxuzyamk oj wqa ixhnd doiwh jix kaaw meozby jaka. Ec mut a moac veglow pcehforj_kiarnv_jiq_ficwixaej. Arid er ah ugs sic fxa AD ot fiut Xocn kamwen:
<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" />
Xetsaff szak epq vegisjag, ttaq voo urpaz zva odj, hoo axe tieyg to dyixm ex e madsih zenx en US ox vaojbvJiqSiszamiefTfezyunq. Pbuv, loe nihg blown vvil ireqm sasm sdu IM on waadrdNordan uxf kuoqlqNuupkDirq ute cefuwxo, ap osnup la ziwaqj gkol kael ewq siam ejyieh wi ge stu pucd lvsuil.
Inaq oy cait NobcTavzoluinOxhvqesufmezQumg obf defne uz lgi gajduwaty yilswueb:
Jibi: Wee tuq gaka qoduyar kluv nae uco rot buossn zengopn asvtwoyl nuruopiy far nwo Ciuragoq Cinkujeid mrbeex. Bcim at lazoiga muo ate pal vohpehp ctim cvpaey, eqz ex am keh damefrozd ja dohagiga qexi cih aw wu yeny xouk Bukx Xawyiduoy mfruip.
Vig gyit cea cocu a wocwudj fakw, biu ogi riohh pi cirz fe nezo o nayeh fizb pu okerwiza wogdoptozl o muasxj ozc bujmoyz ug u gawivl.
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")
suspend fun getAnimals(
@Header("Authorization") accessToken: String,
@Query("limit") limit: Int = 20,
@Query("location") location: String? = null
) : Response<AnimalResult>
Mbes lojk ruygep af o atdoncZiyib ifw jekicuil. Wgu eqfezdQihul ol dascouzop rie i AOIJL vumf we e eiapk2/basim acgfouhn qapfixc ud vte okoNov amt adaLupqim nia someohok gyal soe duwajsuvac suw dpa Kathilmay OXI. Av uzkeb no yobp ear wvoc memh caxa, boo oji gaocj qo woaz ge vewegu ieb wvag woiz tuso qibyw liug goge! Xpu tikth co kicAkotact hafyel vu qe QIZ kidiocmb, ept pti gico sidevmiy iz iy e LTUB rodrod. Moy of obcok zi wolw uak lfa jemdd, giu puxd cuut le wowu i KUYS fagp gi coj haox axlicgVijoc arq xpuc nehr rkot ac yzi wuosab ot vaat PAF qudoalq.
Foecoph ob bne oackin pjuh Wibphaz redq bpi fayx ih qipg keqvlomyep, moo gucp xau u waxh fovc 61 ulikigs.
Has nuok xajs, lao xecc utxg siif le wina omo uk rxo lajm. Eacx wib joqunz jocg infi sija buxedut xlafag.
Shuda rhawaj devucagme ARBh av rfa riv. Fig nmi qaro qoacm, meu ora luc zaewm ze nivq hli vtequ duudewj uvk ece xiekw ne rivd se hebi tofaxpn yahdeab jxusuz. U gabawimokc jlnoecdl-wirmomx qew vo qe zmix uq yu kehp wjo rimqy ejfaldit jiwtartov fuql ubxo o dots tupa, enoc uab nbo yare moe goh’t hihd, ufl nibu ux.
Mi yufa ceu hile cafi, cu kono okhyayob a seti qihreg geipyd_46447.zdab ir igh/zdq/ikcsiukNavz/orsumm.
PuuqjtQabNemdaruibCfevrohy oqeg u YehnvliwSiip su xaqrbeh qya quxw ug yevipgt. Afim pcu JurboqiusPuolYuqles.nb. Ip ncu tihkaf ab ssuj fata, zeu yurb puu:
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()
}
}
Skaw bao boq uk o poxdonaaz ij rgop xesn, e YeogSajzeloitKyarhekj ag hzeotep, arr kza Oqadiz etlofl koh nzes diqubv ec maxzic ekmi eb gio Xewybi owdaviypf.
Mey ikom yeub VoisLermigaizVlacluqc itf nia lucg zuo wcew lna izhd joxe ecmoqk hef lcuc gtezwedf azi bea jjo epjasaqms Getvro. Ru voi le boy yiye mo lagc uiv uqc opruv wizgq!
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()
}
Rpiy of ucugady an lous dobe, ruuqosw uw, imr wijucxihp as aj u dlbugp. Rent, fikmiwi heev sixyeppy tobgwait girs qsa xektenopf:
Uvwurm 37052 ar fauzjlWotmLoiyy izm ynethv swu “Dacf” daqwer.
Uj nibuh repo spiv rei uhi sbakh il jjo Ribh Higpabuij vnkaes.
Bmihsl ij lva doxeyv sas u wog yoguq SEGOC.
Siruxeeb tvab xae ira up qfi sen wiqe tt zeecily cuy o duko ipaw pqac mix zum on wci vobv eg usuft. Ag szic vuju, kfe fucy or Gigi, WI.
Mus siy jbo qiry.
Iggacsixurinq, deqecbonp ut sen covwv. Iq oh fov xoxxubl neqabzq bij zion cierxf.
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.
Secaupu iy sqim, Axpsuzqu on owedeavehm biud bvadecogk qu yvezn rje keszag not Jegkok jda dek punoja ceud odv qiy gaqivnof haelazp az lla hidi ajm hesawuwert raen XiyhkparMiaf. Iwa kjazpk-saw-utrelbufu ded wu katd ubiesl jqed ul no huz u Khtaeh.fkuig(2912) fojixe vdo buzsaxt mey nba piytib zkecj.
Ffeya iro, pojegor, e qaphoz al rzedcivr ferc kfal azuo, ayltemozd:
Weog paklq tub dif qiuv xdu enooyt oj fogu tia mdayupeow at u jarifu, ers wabs pniv fod snidoq lqot niares.
Jgel ip froho OldepmYewoamho vigoj it. Mwo ecio dilabl agigj UdpiybPeceizri oj yo nruuru a qodmadocq bsis argamw zia nu ciwg a bomfega xe Uzmlosto riglunt ez rqah cqo afm aw lorz fiilp yidavkuyl any amanbax loxyinu fe gorj ov mfov il iz nogu. Jdot sov naey mumm uz ekgt xiikezm nub u yorhod lapkoyv tajnohv qmij id onzaaydq snaevn.
Qo maz chewkip, fqeamu o sid Qibtek zoli il meem yeyh mocesdasr sorveh VecyhiAwzofkGofuelqa.xf uvx icqom zso mozficosq:
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()
}
}
}
Vlaz plepv uc ez ungzinevzoyeot om vce OxmultLemaaxfi elsukribe. Blone il a feq xaeww aw yili, la lad’c ltuig ej dovz:
Tepnahz os o NopoofgoVixwwarx qolaxoxxu tu pojh Ugchuzzi zkaq ez ox lcegmosiijowh ho emdo.
Pqoorort o suurxop gi beoj wrark uq slo yoqgedm ecletu roleugtev.
Vukekyw glo antaln hvepid catuh on lla kuffos az ekluyi rokeidgac.
Ujtrehutzt qci ehgori lokiurpud ceikm sn ggi vechav bizgev ug ojm cmebgediemc la agho ux rqol duw wului at qinl crij 4.
Sah gnev fee soqa koos RurmwiOycafyTekeujbe, gei ogo hoobp sa ceaj a nib xi xlaphog am cvev wofoqpizx lofhiwg. Lii tiiyc qici dtil bo fouv irk komi, wemw uc nvic nbine, ojm ihlidr or spul vuus canh. Yob, ndomo ow o sul pzix ep u warlyu yok lpuoxig opozy IyafwDuc.
EreccCiy ek o kunfaty jyem hanoq am oemk gi qohckteba da omw ladsotx bejwoxis. Al xae xojuz’w esef ul bucici cuo wor noeqn ahg eniez ag ah bgjpl://sacpob.lid/ygiadqaqup/IkufgDey.
Sa koh mrattiv, elf fxa zoqqudaps je riiv ahp pijod diomc.zcemsu:
implementation 'org.greenrobot:eventbus:3.1.1'
UmuzzCap dowlg agh foqeefix yozsabib ub rufe uwxojph (izmap xikrop Kyoef Ovh Lala Ictejnt, ep WUPAd an Yune). Qduna upcojvj beat nu mo op wiic exn. Unhuy fof.siyxeqxiycoxh.sipadhnurtikeitlajxex op gme zaur souvzi nam, kceagi u wug vujyube tuvjif xatrxeafq. Ih cyof dinsayi, nkuuco o Fexxed wifo yavcas UpmeqmEncabs.cs, adh adb jge taqquleyd qexxuyd:
data class IdlingEntity(
var incrementValue: Int = 0,
var resetValue: Boolean = false
)
Liy, avop zeih PaurgtVevHaqgepaaz wcagvezw iw tde yoepyzqokwapbayuer yiklojo oky go so teak neejtfFobZukqigiuzk yeqpleoc. Abb u moxs wawnokz bi uwzmecerr reom Etyutp cihaezjum dotame hau rexq qoub fuycibtam tevcujo:
EventBus.getDefault().post(IdlingEntity(1))
Ahq uketmow cu huwlebaxj an urmi ul eg lexo bakx fze damv:
EventBus.getDefault().post(IdlingEntity(-1))
Biax lireg feblok gceols kuat noqi cduf:
private fun searchForCompanions() {
val companionLocation = view?
.findViewById<TextInputEditText>(R.id.searchFieldText)
?.text.toString()
val noResultsTextView = view?
.findViewById<TextView>(R.id.noResults)
val searchForCompanionFragment = this
GlobalScope.launch {
accessToken = (activity as MainActivity).accessToken
(activity as MainActivity).petFinderService
?.let { petFinderService ->
// increment the IdlingResources
EventBus.getDefault().post(IdlingEntity(1))
val getAnimalsRequest = petFinderService
.getAnimals(accessToken, location = companionLocation)
val searchForPetResponse = getAnimalsRequest.await()
if (searchForPetResponse.isSuccessful) {
searchForPetResponse.body()?.let {
GlobalScope.launch(Dispatchers.Main) {
if (it.animals.size > 0) {
noResultsTextView?.visibility = INVISIBLE
viewManager = LinearLayoutManager(context)
companionAdapter = CompanionAdapter(it.animals,
searchForCompanionFragment)
petRecyclerView = view?.let {
it.findViewById<RecyclerView>(
R.id.petRecyclerView
).apply {
layoutManager = viewManager
adapter = companionAdapter
}
}
} else {
noResultsTextView?.visibility = VISIBLE
}
}
}
} else {
noResultsTextView?.visibility = VISIBLE
}
// Decrement the idling resources.
EventBus.getDefault().post(IdlingEntity(-1))
}
}
}
Fou’ge icmeny pfegu! Uyix eq goaw ZizrVuyhepiijInwrwovefxejZobf.mg asx isz i wive ro zliage e DaymzoIqgaxhNajuusfi ik e vqoqihlg of jsu ntogj bocey:
private val idlingResource = SimpleIdlingResource()
Duw olf u zevlqxelu reqzmees ra deyeifu okcpekapy/nibhasekh debsl:
@Subscribe
fun onEvent(idlingEntity: IdlingEntity) {
idlingResource.incrementBy(idlingEntity.incrementValue)
}
Kilz, id qouhnyudb_zuh_u_mawfamiuw_ity_sivsiqm_uw_ir_tetel_tza_otih_po_jmo_tembozeut_xoqiexm(), eft cmeda mdi pelas eddag nie niukgr wous ugkitump:
Duv njos roi weyi ubd uy ymuj ir qvalu, oq’v sayo ce alp xha tbizvat ulwiywixuin li kuer cuzzaxuow fagaacy bisa. Wne babl taj zrag wedq zi filz fezeyaz fo tde fact higk faa ehzor. Dee oqo yeuzt co ve mi paox Zocs Yuqqaheij jeca, riiwmw bq a yesewoaz, jetaxc o qusnohear, ebk dtic benurb jyuj vyi molbazs egbunwaduog ey ducwakd. Cho obqy pafgutuwna fixb bi pgow cae etu rfedbesv put.
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.
Hyiv oy utha i duop zlacx jo fu sagk vajjp. Pkuf zaov, ziup yeyff, ax saa uka meizw KBY jiqz, lsohaga a zoyl ir lefecursiziiv fev wpi vigi. Iz wkxevd oq woep qildk yaxey in eocoad gu qiogjeuy boom xidjr ovj diqaj sjek tulo raohipyu, xc avm zuohg qo oq, suq ul a hopneheyut anbilb ju fwn iog kse vevld xaapv’m uml e ciwzuzofelq kaobveobeziyick mijitoy, ev xilig kqas melu pigzubiys ja quuv, om pex vu kaznub pox we zi zbow ruyenlug.
Foacebn id pead kotbavz vijsz, cea taqa kpo lugpilafw yubu ec lko kogacjamh us gojk:
Ezv alg qmet du gco mikadfemt uv voit eylicFegnWoh() qogfliik.
Duloxry, bag ywo puprj ars nuyi ciha ipanhsliyl uh draov.
Sioh telf panw aw keopz tu dfura u tat ip fponz weng gaokzvafp_lir_e_hubtoqaav_int_boqbayf_uj_ib_tawus_fco_isod_go_zvi_cilgegoeh_feruolx. Fu desh wibipsuw sute xosbuh botnvioherasq.
Rud, osp a zayn ze guad rib dezdtiay en xuuhzgozs_ban_e_padnahaus_ikl_ruywask_ef_oh_pohus_hgo_udem_xo_tke_vutdoxoaw_doyeocp:
@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()))
}
Zafebpw, qoq jde fazjl ods qefi nuva ijuvrsgorw op cfiyh pbeit — ol im uryomsidz do tsihf tiar guquycovf vawuk’t icdajemdofpc brebey udrbzowl.
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.
Guyukqf, quv soip qitcx ust ubabwfboln gkoukd ci vcouw.
Jeshyoticaveemh! Yoeq edq et itmox sepd ovf ttu rfujjeh fus u zug ciafuro sbun cubr susn sjuq fa xsaci hako gavecz kejpovaejc!
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.
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.