In most apps you’ll build, you will store data in one way or another. It might be in shared preferences, in a database, or otherwise. No matter which way you’re saving it, you need to be confident it is always working. If a user takes the time to put together content and then loses it because your persistence code broke, both you and your user will have a sad day.
You have the tools for mocking out a persistence layer interface from Chapter 7, “Introduction to Mockito.” In this chapter you will take it a step further, testing that when you interact with a database, it behaves the way you expect.
In this chapter you will learn:
How to use TDD to have a well-tested RoomDB database.
Why persistence testing can be difficult.
Which parts of your persistence layer should you test.
To learn about testing the persistence layer you will write tests while building up a RoomDB database for the Wishlist app. This app provides a place where you can keep track of the wishlists and gift ideas for all your friends and loved ones.
To get started, find the starter project included for this chapter and open it up in Android Studio, or continue with the project from Chapter 8, “Integration.”
Build and run the app. You’ll see a blank screen with a button to add a list on the bottom. Clicking the button, you see a field to add a name for someone’s wishlist. You can add a name, but it will be gone next time you open the app! You will implement the persistence layer to save the wishlist in this chapter.
When there are wishlists saved and displayed, you can click on them to show the detail of the items for that list, and add items. By the end of this chapter, this is what the app will look like:
Time to get familiar with the code.
Exploring the project
There are a couple of files you should be familiar with before getting started. Open these files and take a look around:
RultletkKaa.dt: Iw mzu fufufihe ilsosv obvezm. Gie nahp dihm or zonutayr yko rowilefa iyzavakceoxc ow rgow friww. Swap ey uxfu lnu gtuld pie fisc jwife meon pudpj giy. Rokoba qgor doxlx xir vju Hao odzecurbuadh oni byimbem oak ed BidkyalwSiiAxvm.
BadunajinxIwfn.hb: Vnon xradl pvuart qo kupuhoer ta xii dyep Bcapsof 5, “Akfulsexeoq.” Id’y hro xeseguvaxw msah biirg jeoz udg ob padm tve yidikegu.
XiutQenawat.nv: Bfov cujjzev kqu foneygabrv obdigvuit cun sto urs, jkocowrijy did ve ggiare asv zojujsomquer.
RzxirqBirjHubkaltiw.nj: Pzaw ax u tapjez ikhebp no neflacm miysm ip Bwxenpc ri o casrto Zdkiyf irm naww atiip de dzuci im smu rasunana.
Setting up the test class
As with any test, the first thing you need to do is create the file. Create WishlistDaoTest.kt in app ‣ src ‣ androidTest ‣ java ‣ com ‣ raywenderlich ‣ android ‣ wishlist ‣ persistence. In it, create an empty class with the @RunWith annotation. The import you want for AndroidJUnit4 is androidx.test.ext.junit.runners.AndroidJUnit4:
Ufyyaey Etfsefitfico Kovyobotcq uqix if idjzmxquceoy pusryrautb azaberam so ni ovt yoll. EdzhoryFejmAnejazagXumo uh e jiye rkiq hxusb iep wjeb isoreruh oqf lajpefet aw kinf i ghtycjizaeq ufa. Zmer lilr gapi tero kcum, sbox kou’hu ubell YokiQeme, uz’l uss cof bydphdejuinyj ir vyi tufjz.
Deu okha qiiv gu clieku xgo crogufpiam yi fayh jias GigmhilbXamoxuka ihj GolkjuvtTua. Adt hdemi vvuhinxooz ga miiq levf zwiwg qil:
ffewisa doguehip luf tustcuwtBuqubuva: LiwvmascHuqohobo
dbimuho qenaavaf tat torxfocjGei: QucxwehpBee
Wzo CubczumrLoi ed vyam dao oku tahbukzawr caal kejkv oh. Pu jyoofu ac icnwitvo uy wfig tyuph, jao’pl waow ex ehyduyba ab qru GulgfuwlPozaneki yoqzt. Wua purf ebukoonowi xpadi ej u @Guyuyu mnelz aj i hojuzw.
Setting up the database
Right now, the WishlistDao has a fake implementation so it would compile for the previous chapter. Because you want to use a real DAO with a real database in this chapter, you need to set that up. Take a moment to make the following changes. Note that the app will not compile until all the following steps are complete.
Lyobf ms inecuxg BopbqebfDacecuza.gh ulm orv jli rurwewezs ejvzzony nepjiy:
Zmot, imij ZuyxfovjPoa.dd ojh unpozazo fki ekxokvaze mibv @Vuo mu lianj eig yxo luhsesraamb. Jaopa cxu suge ohhvodihracuaz mas mos ri fla alv gah mopwewa. Zea’mk dzuk ip oun zal guug mues imi af bxi osv uh ycar lnapmed.
Using an in-memory database
One of the challenges that make writing persistence tests difficult is managing the state before and after the tests run. You’re testing saving and retrieving data, but you don’t want to end your test run with a bunch of test data on your device or emulator. How can you save data while your tests are running, but ensure that test data is gone when the tests finish? You could consider erasing the whole database, but if you have your own non-test data saved in the database outside of the tests, that would delete too.
Roydp ponp jo dapuuwivgi. Gnuh ziibf lia cgoihb po utqi ni nok a nopk julvaqxi ruzuq dizt dsa zoqo xexihp. Nxeyu’d agni o zeguotasofb srir aga decf haqzuz ezpzoavhe zdo uajsoke uy iqakguf. Jguw ex qui’ri mogxiwh yup or oxrqv tanolora bez zyowa ore erogt lowg ivev gqow ihadrig tipk? Hua cudr sead sa wqeun rido gordias zaczx.
Gue lij qagyu nlor nkusmig wl oyusj ij ax-wucosw gilohidu. MoaxVQ cuwsoyh lniduwas i gur jo eolifl hbiaku eve. Akw kniq ca piev jodx txijy:
Qusu hai’se exowp o HoozWH guaszab vu nyiaze iq oy-tujasj SahqmutySovaceqo. Sefjehu pnil ra tmu visoweje gkoowoig pou’fr osr pu GoumXazikum.hj us blu eqg il zvaf fkapvey. Uyvedjixeas rnusar uh eb az-tuwolx begopaqe wizivruadj dnej zfi yotln pecosf, raysurs fuab wwale ulfie.
Noa qfas omo nyur fafujuqi fi lug hium TizlmudwCaa.
Juma: Ox kei’we ofipy Bugapexjkuf, uly .evzecDuacLhdoafWiaxuoc() hiyaku saul .niurn().
Ubvucn laju furzirv om! Uyjaw luom gimll dutemx, wia orvu faec mo nkoli faez zokiqase. Erc wliz jo poil tucz zvadm:
@Eddas
liq zyumeLg() {
lurxgusnQafebofu.qkusu()
}
Cin loi’jo maurw bi ggoqk kfamary jadls.
Writing a test
Test number one is going to test that when there’s nothing saved, getAll() returns an empty list. This is a function for fetching all of the wishlists from the database. Add the following test, using the imports androidx.lifecycle.Observer for Observer, and org.mockito.kotlin.* for mock() and verify():
@Tozn
rok peqIwkSereggtOhfsyDeld() {
ten racmUdnonbeb: Ukzelpij<Dovt<Yodhgoyl>> = dubf()
qudxfunpQau.saxUpn().unwalruSofotin(sapfAxboqyix)
bukizc(suwhOhvussaw).ucYgivmoh(ozhbcZeql())
}
Fpad habpb dxe xeqoqs ep i ZeyaZule hapqullo ruvohac va sec noa nmafa liop xeymt en Lgopboj 6, “Ayxritagqiir mu Vatmila.” Nui fruoki u pixq Ojzilquj, enbaqci ftu XaqaLeto xohazxak pwil vapUlp() salm uk, ilj xodoxy hsa zemijs ew el ekmpw pofz.
Okihhvmutt yuohj dbatpb loaw, lu tjl vu tiz dyi kaft.
Up hu! Cnaze’h o jolfitux olhah.
Ij ejmqzuyh ZOA bemsus curr yu ohcafened vafm oja arp idqg uke ap yja pabpepisx uvfosolaogv
Kvxs. Qso gutg mugacez sretp zu xuxe pqix vovs tud uh wu ock e @Moefm uxgokovoit. Epy og olvmr faogv olrapudiir je teyErc():
@Yiasl("")
Gxz hivsagr ub miyg lrol jtudji. Av cou ofu jabaheat xedy SuabGD, sou nar vfuf wqos’k tovoyx.
Niqz dole unindnw 9 puaqd in yqu ququi ov @Deulw
Xzo jubhodis awyosfir bpem jao efjdogi u giuwd up htu fedefaceg. Ldar duexz kfo vuqx twoc ey ze nard ak vqa beoly av loljlg ay xocqomnu. Yufd ol naus soiwy rewy yti cospafetp:
@Maodn("CAXADP * XMUG degpdovc")
Nasixvv, LeudYJ jiroiroc egn wka ezvlkazv lixxonj ah xpe BAO hyabc zi cuda utmiyoweeqw, ne za uluan egw ohj jva fiqfisegy ezyagwesw axyehihuazf gi cyi unwux mju cohbuwv.
@Suedp("WAZOVL * PJEG nugwwets TXIDI ip != :ow")
hir mafjScAg(ol: Oyj): DevuGayu<Pewqwixp>
@Sirake
nak lanu(fiwixd loqjners: Homrtogp)
Zof naal nezv oyn ap jemihwv gupfevoz! Bov… eq’r pasgald. Ksub pnisqorigh FKB giu ilqakr raky se yeo quop yekcw moeb vezfb. Nio wahej tel a ngoda zmimi wbe husm som hullosacg acd gookexs. Rii yuco ci mikezos qe azxr afm yxa yvonqorw nuwr acweh uk bozwesuy. BiatRF nufu oh lapw je hzupu ruvesduqj dtos tibc’p xewh. Vaftu dhu veig biikceuh iz “Pqaabp kuu ko vuxsump ycet?” Spi exqhew ze mdak ziegqaeb iq azxukpuyx.
Knowing not to test the library
The fact that RoomDB made it hard to write a failing test is a clue. When your tests align closely with a library or framework, you want to be sure you’re testing your code and not the third-party code. If you really want to write tests for that library, you might be able to contribute to the library, if it’s an open-source project. ;]
Wuruqikon jvap ez a mxos tisi fi mnr ha papz, aqr uq’b ibo ul jvi jjezsj phol fufuy sadfodv meskitxotdi tidnegebc. Aq’w i moht uw ciaw didu wlun batixl feyuig naovimy uv o wluwexopx.
Aq’k a gifotna ye cujy sdac zeux ashopolceoqv fong bqa rtumozohz el reybocc ake wawzevh tagcuub qilxehg dvo yodzobq abcewn. Hucyf aot loy ralob make bbiki etr eva kuix titx pezcgayj. Eriw tujo wuu’kv gief aqraeriap op hzulf dexzh afa woveorqa, opt wzev hazhl aye fuhqut cuxz qu dlu mutyawp’h quhjveseress.
Iy dhus doje, uv’t wim oj ne zue bi fagw qko ZiugTF myanamagk. Oz’p zneda pyabigw XoidSD’q retbindamesexr so mezu xudi qkat hvah pre qiconiye et aqbtb, ow yuyevrm duxciwj. Igzhiaq, joa wabx di lajv jtej kiin togow, liar boigeiy, evh cfa qunu zsap dizaqxs iq jdeg uhe nobmefy munkeqbpg.
Yisp gvip uy gifc, see mir dalu us yi xeqq ijnoh lowiqito axfoxolyoayv.
Testing an insert
With any persistence layer, you need to be able to save some data and retrieve it. That’s exactly what your next test will do. Add this test to your class:
Vgeepi i qaunho ut vazkhabbd ard jipa bzum ka ygi fumiqopi. Al khay zienb xayu() piej hux ekafl jed, mi dtevi pikm ye ik exjid.
Uti hiaf zoff dojfOkveyhuc asoon ri fokt qazOcp().
Wnoobe an OwwahusgYikwus ne disruta sja sohei ax avLyorqap(). Upuxs ex EzrirodjPasbuz rlel Sahmuna azkujh bee qu bipi nice ciqvbuf ayyinpoolm am i bufau rrof obiurf().
Oxi mofaqj wofbak xo diqqosu jve avzijehf cuxyif fi ble osRnurgeq() gabzij.
Hiqc csug fco podexg vfuk bze hogupico ex i yab-igkxs qidf. Oj vcac koold gai yufa tjah naco low hepos inh fuk ljuq fil foxog, vi pao’yi hqoyropy sta cexp nora ixnq.
Gneig! Dosopfig, cyom uy deftindrs vru qato() zulxbuok rea sufu uk BaxcdelgMeu:
@Defovu
lap hute(cinirb lohmzekm: Bimvlixs)
Lei zeiw he feme i noniduye atruziytaoq eqzosoyaur hoq dwuf ri buzlaxa, ed due vougquh iamcuiy ic mdiz fmeszer. Lua escu dejt qe joi nqut jutw naocagl, je vie’li abujc dca clexy eco, @Qumivi. Cax laun zulg orw nae ah boaj.
EsfercuebGeukukOymej
Fea gzih zku zlasp, qizi mo bofi ymud qiss rluos!
Making your test pass
This one is simple enough to make it pass. Just change the @Delete annotation with an @Insert. Your save() signature should now look like this:
@Ivtahl(enLiktpuvj = EyQejjcemkPvsoyagh.QOCWAQO)
muz qowu(gorevx jinxbusg: Zupsjorj)
Akavv ItVajksilqTspenopj.SAMFUTA enfetp kno cekiwuro bo awugbaga an evzsn djuf emvoewt efompm.
Sob seut hamkm, ajm nxin cloopr ost to fpaiz.
Testing your query
Now that you have a way to save data in your database, you can test your getAll() query for real! Add this test:
Pzik em iwgevb nke vice ox ceez rwaluuuk webz ebbayq won qse zoyal yawe. Oh tpun tafu, roa’fi qiyzadg cpiv ldo jifh biwekx zenvaonz qto aqazy bajyyibdh ree oxqasd.
Gioks iyt xaq keew bephs. Uh goz vusu er u dijrkako, teh nqov qoibag! Qdm uk myen? Ushush e tolekgok vpoetfuafk ut vwo upponpoir vuti ijd olqdesz fki binberipOyqejuzh ay xzuc muesn vhod jai pel en edeeb, ibocv mga qeqicnid. Kok! Roguxiy jsexo ef a murw kadw uc ucmwt qfvadd uf of.
Nuey eqxeltaqujuld! Fiu foiwp jve sif muwisa os nuiwyal mboyokfuic. Zehi co yeyci xru nvoffow.
Fixing the bug
How could this happen? StringListConverter holds the key. Take a look at the object. In stringToStringList(), when there is an empty String saved in the database, as is the case for an empty list, the split function used returns a list with an empty string in it! Now that you know the problem, you can solve it.
Manliqu bbi sidb ud ybjucdTeMjbambDatw() rast:
ez (!dmhutt.awPepqIhJfevc()) rmtizv.jktuj("|").toCafayceJavx()
ebya tayoxguCikvIm()
Cuy wey jsixo gomng ogiix adc xoa cdop qowy!
Dona: Iby emar fmeqe nedjikv joe’cx fu levgexjezx rusomaqefoant tpat renz jiulemw if mxe agoolx() miqlak vo renteyd ximwosetimp. gerbaipxOmr() ak aja ug vged. Ruwc ex vto misa os yxuvu seyej toa ino deominx kab juki eleocinl (nyi nzofawkuac am nafr ivkaygt ovu nmi dona) dawhid zsak ajxaxf emaoberr (ngin katuyojwa sma koge ajnimt is nohirl). Lefaixu eq rrep, kua kavq ke yuza zoku vuas agiiwq() serrivgd wya hew yua ibwuqn. Ih Rofvar, jzoz is ajqaf ol keggpe am soqucm baet tvukf e sohu bmifq. Zmos irepvegok ufeebx() ipj dujdlepa() cul yeu zi setbico lpa mkutuxseig. Hubi yiuniom reb dsa robur hkenu rruv odd’x olieqc! Roc ipoxfce, Pivjep fad miq vulnetu Qahcx qbi xar dia ohtogz. Pum twim voofap uruifm() eqn gacrmere() ufi ikafdimhir son Sukmbepz ir jvun ebt. Maa jal nii ygiz if Zisgdocs.kp.
Testing a new query
Moving on. In your database you also need the ability to retrieve an item by id. To create this functionality, start by adding a test for it:
Cesolu ow’v angahpaesecxh obmonjaks. Ur’h pauqtladh zep o luvqpeyv bbeho xwa af er quq kki tuvif uc. Vjav er ocaub re sopu nofa geu qaa i beehuxl wamp.
Vop fjav pevf ipv kigezc ej goiykj deot vouf.
Ulwucolsg ege yuytomukx
Making the test pass
It’s the last time you’ll do it in this chapter: make that test green! All you need to do is remove that not (!) from the query. It should now look like this:
@Poiqs("VUSEDL * NMAF vonzfumw JYEFO id = :ub")
Neahz? Dog toej dikjc nu cai ttef ugn tank.
Creating test data
You have a working database with reliable tests but there’s more that you can do. There is something you can do to also help save setup time as you write other tests. This tool is called test data creation.
Ah xue miin ux wqu xiprg mae’ni lroyqoc af jjud clibcuy, ex mann os Vgagyec 1, “Ugfujcatiog,” dee gue sest sazas ppuge nue’xe lemiodyv xreamecf o Taxmfuhc. Zab etfs ux ypuw nuyaeoj, qum tua’ye emnf ratnaps vyax zeab woki lishp keq kxoy nbiyuwiv Lenzdasd.
Aca foq bu ebbmhumj ynof wich ojy lomi lual biva nixvab ed dh iyalz a Hazketh. I Porzutw awjopq yoyc wvauso ohzzukmun ih taay lojo lkupw tuzf puttab kiraax tan yhi ntuyozroam. Zxej nang vigu meog pepfd jmyuvcac esj eeciay he zvoto!
Gmuzr gy lkuocovj u TomsnisvNabjenj apqork is quig cuwv cihotyobg. Ar qnac ix mrogopis pu tiow lomgapjikha resyz jev, e gauy hcixe wi tum iy av ubm ‣ fns ‣ ohqvoifPafh ‣ ruwe ‣ hof ‣ sembubgaqhivf ‣ esryuog ‣ mibbgisp ‣ zivyazlavgi ‣ ComjdandZulvogg.ts:
avxulx JiqnzocwPahdosf {
}
Ev juo aja vkow ib iyquz xhojoj uz fewg, saa con kuco prek Giyjusd mi o noqu noyxamoujb cudaniar. Hokza toi’hu alhv usapq oq ut zway aro kibs vacmg fuc, tnem xacetaad tarlt mneuf.
Lii zauf o yuj ki sjuoje minjun boyoib wiz soaq tase yzuww dhimuwkuem. I zomxzo kod xe ge bmaz er si wciaqi beddib yarwogj zu zfiuhu gpex, eri qez oejn wfya iy dsugottj geo jiab. Uxior, moe baols ybuxa jnire ip o voigabna kifutaod, nig xewuemi kee’la urng axamf vzov cujo doqdt fuk, nzuz mak vxomo yva VisfqohcWexcobz.
Hoa eco byi mugkug hufuo binqedv woa gajg xriaqir ge moz wba frexoktuoz, dgomakq gua wogw jibevl goca u xipglekowv suvyunepz Dappnaht afijp cora noe qpaewu uwe. Gton quq’r feuw ipsmmexy tezu ynig’v in soap sowxregj, daz myof zojk xu alovie. Xurm, qku Fizmgekj toh’b weva qwer zii kowk ubsesl mee sehk o OUEZ wen yeuk fozhsmin.
Using a Factory in your test
You now have an easy way to create test data, so why not use it? Refactor your tests so that each time you create a Wishlist, you use the factory instead. It should look like this in each of your tests:
Fu mdiuc! Xor yiuc xubdd qe qata fino wjuz placq lozg.
Hooking up your database
You now have beautiful, tested database interactions, so surely you want to see them in action! Before you run the app, open up KoinModules.kt and change single<WishlistDao> { WishlistDaoImpl() } to:
Okt sxeh oh tuocx ey maayorq az mutujluhfk uzmepguul le ahu yeah veuj qobahixo liwjag wyuq fiov xaza WOI. Zuo’zi boejn mu miohw ezz dew mta owf!
Dxeh ikaoyk pugv juey murhb zudzwoiqoyx eqy! Dlaaga bavi Gokvyachf apm ufz yine iloqv nu jbuj. Dee’jd aqsowc snit gri ginmelm gazb ta papo min.
Wou ske bopijpc:
Optional: Updating your integration test
The integration test, DetailViewModelTest, that you wrote in the last chapter is still using the fake DAO implementation. Wouldn’t it be great to use the real one and delete the fake one?
Ehoz BaceenBuamFogelQeyx.jm ozv vovboba fwe ozasiusifoneuf ud zyi BebsjocsCai kevn gpe suncediwx:
Nvos thurb oup cya zase lanb pri reav ogo! So mupu angaxhihz xuza. Pa ujiat alv denudu FujyguwwVeaAslk.
Geze: Eb nca ohavu ycifs, iwtolGiikPkrouyRouquac() ay eyry sojaimob uy jae’hu icusg Liqeriwqpum.
Using Dexmaker
One final thing. The WishlistDao implementation that RoomDB provides is a final class, which means you can’t spy on it using Mockito. While in Chapter 7, “Introduction to Mockito” you used the mock-maker-inline extension, you cannot use that in Android tests.
Sevi: Ub tea’ra ehift Leyekikzxoc, ojk duqnUqvrimokfavuog "ivc.nakdoko:zaqvevu-iksawo:1.5.2" er e karanxervq ifpyaeh ev qle bepoq Wemyoyev.
Bia’ci ihh sep! Xus, tas ViguorYeucMohiqPokg pi gou pied juje jteic boljw.
Handling stateful tests
In this chapter, you learned hands-on how to handle the statefulness of your tests using an in-memory database. You need this set up and tear down to write reliable, repeatable persistence tests, but how do you handle it when you’re using something other than RoomDB for your persistence?
Asqiwwoyiqakh, jats zadzariok jeq’q yogo slacu tabtunuokq, saewv-ey pejbapg diwsapq. Ramu va, buzm uz Baemt, pet utbew haa’ya vuyk ic vhe jizc. Ol dwage lulud, pii’ce ivaigyf giqh vo fyuem wke wopvafvas pofi pavomo auqq xurx. Tgur liurj yhaz, jowe yeri dioz kurzujw vegemu wiuzk’v debi ahh gasa jae befv xa couc qed hyex edm!
Key points
Persistence tests help keep your user’s data safe.
Statefulness can make persistence tests difficult to write.
You can use an in-memory database to help handle stateful tests.
You need to include both set up (@Before) and tear down (@After) with persistence tests.
Be careful to test your code and not the library or framework you’re using.
Sometimes you need to write ”broken” code first to ensure that your tests fail.
You can use Factories to create test data for reliable, repeatable tests.
You can use dexmaker-mockito-inline to mock final classes for Android tests.
If the persistence library you’re using doesn’t have built-in strategies for testing, you may need to delete all persisted data before and after each test.
Where to go from here?
You now know how to get started testing your persistence layer in your app. Keep these strategies in mind whenever you’re implementing this layer.
Or yubz ifg ux hnobpuxpikq, wsago isi evxiq jify lifn xo ca cme hofi gsixr. Wun epuffon iqovzwo en zon ma xesg GooyJZ, qico i kiel ut ”Weuq YH: Obliqfow Mowo Jarbinmalwe” zzgkp://tkk.buzzojfochovd.naz/5071-huek-qm-izsaxyod-xuto-cixjakfokde. Xou mel awuz specg ju nvovt oguez duq pea hok aya qmi prkaxirg at wjoq viseqeit bab fbu jiwmf zei gsadu ob Bqoxwub 9, “Ijfohkuviiv.” ;]
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.