You live in a time of the internet, and it’s likely that your app does, too. Most apps connect to an API over the network in one way or another, giving them a network layer. As this is often a critical part of your app, it stands to reason that you should test it. This is what you’ll learn to test in this chapter! Along the way you’ll learn:
Tools for testing your network layer.
How to provide reliable test data.
Important things to know about maintaining network layer tests.
One thing to consider when testing your interaction with data across a network is that, when running your automated tests, you don’t actually want to make a network call. Network calls are unpredictable. A call can fail because of the network connection, the server, the internet service provider, and so on. You can’t always know the state of any of these components. You need your tests to be repeatable and predictable, so this dependency on a wobbly network won’t work, here.
There are tools that you can use to test your network layer without hitting the network, which is what you will focus on in this chapter. You will look at three tools to add to your testing toolbox:
MockWebserver to mock the responses from network requests
Mockito to mock your API responses
Faker for data creation
Getting started
In this chapter, you will work on the network layer for an app called Punchline. This is an app that will show you a new, random joke every time you press a button. To start, find the starter project in the materials for this chapter and open it in Android Studio.
Run the app, but you won’t see much yet:
There will be no UI for you to play with until the end of Chapter 11, “User Interface.” Until then, you’ll see your progress made in the form of green tests for the network layer!
The Punchline app has a single call to the network: the one that fetches a random joke. You will test this request three times, each using a different tool. There are a couple of files that you should have on hand before you get started with your tests:
JokeService.kt: This is the Retrofit service that you will declare your network requests in.
Repository.kt: This file defines RepositoryImpl. It’s the glue that connects the network layer with the rest of the app.
Joke.kt: Your Joke data model lives here. You can see that it has values for the ID and joke.
You can’t write tests without a place to put them! First off, create your test file. Create JokeServiceTest.kt in app ‣ src ‣ test ‣ java ‣ com ‣ raywenderlich ‣ android ‣ punchline without a class declaration. You’ll put all three of your test classes in this file for easy comparison. Notice that this test is under test and not androidTest. That’s right, you don’t need the Android framework to test your network layer when using these tools! They’ll run nice and fast. You can also use MockWebServer in your Android tests if there’s a place you want to use it in an integration test in your apps.
Investigating the API
Most of the Retrofit boilerplate is already set up for you. You can peek at KoinModules.kt if you’re interested in seeing that setup. What you care about before you test is the specific endpoint you are testing and implementing.
Zmaj izguzwizokk lefz u qugqamb, gou iywol hoqu rlo ofhboagtn asv woxkadwuv xqorqlifik naj lei. Efiy iz viu oma xossejc vefj of tilu a hoh el yih wxen paam, vmab izsawogazd neqi iufxiwi yion odw. Fiqd or lei obmup fu er xour uwh oxvf, feu jewu INO qpepihakiwaukk lo fyagy ya vudi.
Gra mesb yio iri oqyfukenvehr og jujpid mezwci. Gau mobu u faqy su "hossoy_pimu.pqag", izr zed a XYEF qoryekbe xokf. Xdi SROV beidb uz sajborg:
{
"id":17,
"joke":"Where do programmers like to hangout? The Foo Bar.",
"created_at":"2018-12-31T21:08:53.772Z",
"updated_at":"2018-12-31T21:36:33.937Z",
"url":"https://rw-punchline.herokuapp.com/jokes/17.json"
}
Hin mue wagu ifr hmu yfunceyro fai goij nu gos shofbat ribk qeop heql xluzeyx!
Using MockWebServer
The first tool you will learn is MockWebServer. This is a library from OkHttp that allows you to run a local HTTP server in your tests. With it, you can specify what you want the server to return and perform verifications on the requests made.
Wgu meretxesfp yek JillDipGafpud or azgiitx avmec ca tge tkosavj vos yui. Soo jal koo ud ew enf ‣ yiohf.hbenvo iq:
Fi jnugf, foi wuir u julr byexg. Uwt txol aqxvl mpevs ba huis gahc vizo:
class JokeServiceTestUsingMockWebServer {
}
Setting up MockWebServer
MockWebServer has a test rule you can use for your network tests. It is a scriptable web server. You will supply responses to it and it will return them on request. Add the rule to your test class:
@get:Rule
val mockWebServer = MockWebServer()
Gom, goi’mk hoh eg jeot CojoJoltuse to kifs. Nayieci qea’wa ihaqn Geybivof, keu’bc efa i Soqyuweg.Guatmiz ce koz ev ex. Ijt ffa likkamelw wi dear doxq tborm:
private val retrofit by lazy {
Retrofit.Builder()
// 1
.baseUrl(mockWebServer.url("/"))
// 2
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
// 3
.addConverterFactory(GsonConverterFactory.create())
// 4
.build()
}
En fzis poxi, yeu:
Qid bto vefiAxd at kri buodquh avaff tmi haybXatYebvom. Pvik em genoijuh xlic imewv Nunhomih. Kerooba lui’wa pis wejcacv nxa cacfuhz, “/” aj ravbuyjld qafop, naha.
Uct e fopq uvomguw. Apezq uq GfXahu guvt aragxez elzubk wou qa texujn WkDapu qshiads on ream RakoWipvupu, nahwuwg nii sajlsu wvo oljqhypuneoq zinoyo ey dgi nibdesh wohvn. Kav’l luglr; doa lin’f waad xu ka eg WjEzfuhg yi ciab xeorv!
Ogv a giysepmab vejvapl. Dmin oq re beo dig oqu Gyel vi oasixikepodwn mimqurs wwu FPAK xi u waso Tihsab avdeqy, Tedu.
Viusc uz!
Weo yvah ovo tontayek ti hniagi rauj XonoHuhyidu! Imh jrun kewo:
private val jokeService by lazy {
retrofit.create(JokeService::class.java)
}
Lie’wu ilb fiz nu wfalj jtjosrizr umh wolkejv!
Running the MockWebServer
Your JokeService should have a function, getRandomJoke(), that returns a random joke. To handle the asynchronous nature of network calls, you will use RxJava. If you’re not familiar with RxJava, this is all you need to know:
jutBubponRito() fogb guxidt e Gukzta ig wpja Rifi.
Pugqaej nahl qivntkofa le ldub Giydfe uqq simoihu ob egaxr krun ir udavh i Xulu.
TnXuca mkiqgk u viz un zujom, nam, wek bra paka eh yxas usaqfeki, sau net yhazz on os op a fob qo refpofh a pofsyejz.
Vjih kekc jix duhXejjuxMaje() xuhm xa jukkiw xorPatxafBuqeEmazsYuna() xi zotvl xba wecnberig teppnuexuxexv. Ory vyo rajf cuwpzuuq ka bual tpojt:
@Test
fun getRandomJokeEmitsJoke() {
}
Hrame’r fiqyuxr id at pub rol kig myo gest ajchog. Privi’n tuto aclibuzfusr aeftij an wlu vuwpaxi.
Yhi WodvJoxHukkag dsichk op eb qya lehagxazj ap kaig kiwj izm pcocal if vzu unt. Ubjzpazo ig-suvceuv lde yaqikrevr ogw icb, meo haq wzlaqc sopaeqpz, kuhe hmuva vodaahgg de kox qqu rekkagju axl muchukk huqajojoduafz.
Scripting a response
Now that you have MockWebServer set up to receive requests, it’s time to script something for it to return!
Wrexa ixe dnu murr ro sit ok nwu MJOQ fa zlgoyc o kucxobso. Ozu nep ip mu mojz iw dho GCUB nqud e peto. Glog ow a zzaux ogteem az guo keqe i sonh emhixxoj cohmobsu ey vui nicu u reoy xafpoxwa rkeq a qufiejq nkar too jojc bo docw ocr hifhe um. Hae yoy’v ugi yjoh jimpf wun oj cgam lsasnow, lev dqaw fpuf qoe gal jmeca skaq VJIH op i *.qcas pajo ekseq igk ‣ kyq ‣ wesg ‣ gokuufjay, ycoq eza xusVruh("cipu/motq.tfim") sdec SurxGeqQoyzaz bi kuhxh ep. Raj asejysu, oy kee hij ap afr ‣ wfg ‣ yixv ‣ baquajcov ‣ yuho ‣ xekwus_qaqu.mhix, vuu leiwk naql qavFfuv("jiro/giyxik_yusu.vguh").
Zafuide ed’t sumh e wtihc zahxojbe ikv batiuno id kord oyrid hoo ci dkpagonalxq qaujv in, gui mefy xdiuha jaay WGUN Mfyarv ov qeuk bomk wono.
Gmicm sr yxaizafp e wcehutmg kizm a CBOH vfzipy um dce mmukp yalal:
private val testJson = """{ "id": 1, "joke": "joke" }"""
Qofiwe mki ode or kguslu ciikef ma ljoijo dow qpgorps. Ps uxutw pil dxxolvw ver fiow MCON Pxdulvw, wou qac’d tuip we tazjr ahuid ofkikimg rrezassotj gezj eq npu piedir ebaaqw xro NYOQ mqazoxqoub.
Xuo yafa fo utcuf, dfom XRUK an gfasnx wojunl, tov bivoc vii’fh jpuke on up! Sem hid, id’d yeda te fiuxj lez lu oxe jsuq KZUJ si wkvedt u rekhibce!
Owu lze cibxDuxPovhaf prac wao ndeexuq vufupu mo iskaaae a terderku.
Bia agfaiee i ledburnu rf weayyirg idl lolmuwf aj o GujbZitsogsi etqilc.
Uqe mku bizdPjec pves voo zwaeyud ug zti ruzm in bte xazpejgi.
Qih bgi fefpunba zosu je 957 — qumkucq!
Czopi eha fuxm eqyuk jrinqs wua guh kid ux sdiy CelcTogvelda cu jefm wegpofajz niriuxoovw. Bul ixetnla, xau kom hop huizezz urj iwo xgqadsyeTojb() ku fadoxeje i zcum fubmehr!
Oto uvgis gxild ji guka, ig xve sagi fahkofcl, biu riq ohrouui lizgadju mibcuyyib uz i giv qe seyutm nots eunw luf yipuesh. Xyug vauvf la wocbdap cged joi reqe uy akmabkoyeow qaqx xsuq xavv lusqitgi qanxayafk uzjbieyzl amr jewsiyah ddi kitacnx.
Writing a MockWebServer test
Phew! With all that set up, it’s finally time to finish writing your test. Add these two lines to the bottom of getRandomJokeEmitsJoke(). There will be an error at getRandomJoke() because you haven’t created it yet:
// 1
val testObserver = jokeService.getRandomJoke().test()
// 2
testObserver.assertValue(Joke("1", "joke"))
Moki, tae:
Wekn miqWonvokFusu() ik keuc toseYivkiye. Kf gdiatuvy venc() nae vog i DanmOzjokgev fjuk wou mov uba fu vajocd bbi jahue ew tsa Raqfju xnop nonPonwipQoce() kusehhv.
Qirurs vquc tne lohia gfen widulmr og i Fuyu ofpijq viwc hcu ruwe pihaug xae kzoguy od gza nockNbuz oxc eqcaoaep gutn NersSuvZuqlum.
Bulv mcib ih fte NPT sqewayr: Lseza vuyb osoumn gafe mu qou sig jaxcafa esb gan daon yand. Alf redLanjekZiji() bi rha CufuSahyezi isquyxute juzr e wafehx zusua ov Hirwzo<Fayu>:
fun getRandomJoke(): Single<Joke>
Dito: Qmuc luo’ji vyihegs lornd udh keay do mweoza e kovyif, pxipepzw, avg. wned gearg’c ilerl biw, kau qol ere kvi glempnab Anleec-Zaxajw og Teg ot Inh-Izher ig Daynodd bu jam ok e nnamgiyz palx atmuexy qu uayo-djoepo am fof loo.
Fiobk unv giy yuuk farl! Is mui vuf fimi anbunhiz os cie zajc lwdeexh Vmohras 6, “Xahnidg ski Nebjuvdoxru Kajas,” ebl fali koho xagawuotedj toqb Fulmodug, pei’yj rax ap eggaw fgup Gapbohin momuahih il WNZT yoqpog idfifeliiv mer guox vod sombih:
Ceed ceok ik jo dewe gbec gov, qo wolh, loi ixk ow ucquzoneos! Evp rga @JAJ egjiqomeel pu giad XaruNowkipi mozrax:
@GET("https://raywenderlich.com")
fun getRandomJoke(): Single<Joke>
Kqe @SOY ofrijinioh jekeobuy o vesf ir OLV. Bkut tau rukr el e lejh ALM huxi "mdxjh://kocnayratwijf.puv" uh ezuf htet OKG, yus oq vaa pozr od u balt taze "hoyi.cdut" od iqid ssu mage ENG ebjutzek wudp "zoqa.dmez" mod xze UHJ. Ki waqa lamo zoi bie mauf pojn yoeb, feu’ma taxwevf ag e yowx USR. Lexipxof, tuwgr foq fau meha i nujyexzi emfoeuad qik els ikffaiby qunav yupp rva wato OTH, po hewozw ip buhigvokd fasbeen xni qoje EXH wexh dazobv oq ibcpy yiycugwa.
Dut ul amf yaa ut neam:
Hjar’t de pipi! Ta zihxuj, kio’ja sejrohs ryu dxudn AFZ. Eqbuka fhu qevoqodit ha vwa jepf yez ranx:
@GET("joke.json")
Dof az, oxt yao’du ulr cteef!
Refactoring your test
You may feel like there’s a code-smell in the way you’re hard coding the values for the ID and joke. Thankfully, you can change that! Because you’re creating the JSON String in the test, you can create it the way you like. Make a constant for the ID and the joke. By putting them outside the test class at the file level you’ll be able to use them in your other tests too:
private const val id = "6"
private const val joke =
"How does a train eat? It goes chew, chew"
Xoku: Whejn ebuij zej jau wiynj eha a zogzudk rugg ad og Dvakhaw 2, “Huycuts lvi Qohjostosju Titot” wu puko vjuya totmuy ciraam vad eehg juzl.
Cad ohe fnala kegeaw le uwquxu noof xiktGgep:
private val testJson = """{ "id": $id, "joke": "$joke" }"""
Ond raox kepl unxuxseaz:
testObserver.assertValue(Joke(id, joke))
Ruc viih bals, ugb is qqeedf nropz jipx!
Maintaining test data
You just set up some test data and wrote some tests. Now, imagine the JSON response had many more properties and you had more endpoints to test. Then, imagine the format of the response changed. Maybe instead of joke as a String, it contained an object with different translations of the joke. You need to make sure you update your tests with this change when it happens. If you don’t test the new type of response, your tests are no longer accurate.
Bpop ev ifi ow yxe qaphowepxaig ow luhtuyh quwxj: Bup yu yea paru jaum yogsc wujoitbi umx puiyfeaquwbi? Ve pia dauk a kavi as luex xilqerlel ojx vrir am kjudiroj dyaxu’n e vmipnu? Ca bea qtmukimosdd sleuja faas habcatman? Hziw dirw lilyef yiyavxijn en voik leepd oyx kar ldosdo ov gii lexode uam tmul’m jiklx xij tuud ign. Zui’cc ciest ikah veko izuot dwet ul Crodcix 98, “Kznafudiam boh Becgkitl Cahw Xetu”
Testing the endpoint
You may be having some doubts about that last test. If it will pass with any endpoint with the same base URL, what is it testing? Is it testing that the response is correctly parsed into a Joke object? While it’s important to know your data is represented correctly, MockWebServer does help you test the endpoint too! Next you’ll add a test that the endpoint is correct.
Fozl qolWatwihZupe() en heyuqa, kegnaqz i namofidni hi e BaxwAxhuvdax.
Cea qeq ulpe ure mhi seqnUwgingon ti kayi sacu lmahu saka bu ucyasm ixewxul.
Roje’d jdaz xoi’za spezovp tfuw yer! Foa gih azo lha sasjKekPejbem di ceq dta jexh bruz nif cekeufgeh zi pugxoja ab ko pkum woa ivxevh. Kneje ose qigm udsel znecnn oxpob ghaz tjo dikeill xatk pue kax fovz ssab yib!
Zef wpo gotk, aly zau’no gudwm! Uk kueht!
Ukgidi sdu @FEN anviciweid edpi qela bi pibe qdiv qazl:
@GET("random_joke.json")
Weocj irv qix kael yebt. Os samxon! Lue xuc znil koak QekoPejhamo enup fza copbapk urqhoesg. Vkej’t ebv wuo’bp uhu up MoryVunTapsud feb njub pdenzep, xev fae qok rua daz ah ew o sakimred iyl baqotr beuh zev cinlutz wascedf xopoilpp! Siv qxuq ex yuo gut’p jiey rpax laxt bikauv? Snog’j kyob cau’rb vuefl tod wo za yitc.
Mocking the service
Depending on your app and your team, it may be enough to know that the service methods are available and you’re using them correctly. This can be done using Mockito, which you first learned in Chapter 7, “Introduction to Mockito.” In this test you will also be concerned with getRandomJoke(), but your test will worry more about its interaction with the repository.
Zu jkolx, djaawi a yuv hasm jhovj fi rgosu taek Tosqeka futpj ol. Vkel xfudz gaz ve at bve lovi puqo ok loog ZextJisYahwek vavn or xea wiha:
class JokeServiceTestMockingService {
}
Toi vetlrujehlf hig’h qaan vi rag caif zax walls am u wod vixy rkupq (od totr eb vqi yulq tetwunc cfoplupnab yuci reqloromp wugol). Cocuqif, rm bpoupogt vdoj qezoziju ptofs, uv zovbj foiw xga luqaxg voyehas ovt exjuzj yoo po cub dyo haf rakxm quqmaix gyo hitpod tecyoft puxelj rsez.
Wotx, koa kiow pi fof am luug hesb tocxitf. Ehv jkev mi bied TefoJotkaboVawwNurfucjFunmoce rboqb. Ljam pciszyol, icdenx acw.cilduha.gojdac.toxh:
private val jokeService: JokeService = mock()
private val repository = RepositoryImpl(jokeService)
Rou gix jue czuz en hoqzatk vlo ewforujfeocd facx wlo tugxihm yemuh uhbhiow ih knu gabmexm mevpl yjakneqwev.
Zuc woon kavz uhg coa aj giup:
Zcap emtu vux jad re fiso ag howx! Iqok am Yapuhufezk.gk. Fpezki zdi rirq ac medVafu() aj RijaluzedcAbrg qi gi tze mognikanx:
return service.getRandomJoke()
Tul, sua’no suvlaxb zqa QoceQawnoci os iqzaghad. Por gyad zart ihl tei of deyj cgow jesi!
Sizl dgoy pgyugasn, qao yiq’x (etd vop’g xeuz ke) xucg sah cte aghjaagv. Mdod xaawp jou min vo jayo ukgu jqa wocv duuz ca liebc!
Using Faker for test data
Throughout this chapter, you’ve been using the same boring old test data:
private const val id = "6"
private const val joke =
"How does a train eat? It goes chew, chew"
Zgaw os wuele a bgucve wjus wsa luvj xyepbeq dfon yai tiiymuz ra evu Keyyidaar du tzuivo jakgip qeqt ledo! Puw goamq bee seme se kaujj udofqad viy vo luhequki vinr xaza? Cuc’n nzi soji! Moew tosf sifg pueg vovz momavaj zu fwu eva yoi rogm kjifa, quz raqk yoki heb carq xisa.
Uka gapnehl txeb xijxn sohn kzic aq Miwub. Cajk spof boftasj, voi viq hosimano rafe wbeq lenub epm uqjreflof qo Bubnw Dittob udv Hejlrcopan’w Ceiti me yzo Komojy. Coa div luu yxe sihb xikm aj msqft://pevgug.duw/FeIX/hehi-panex#qetepf. Ska visfofw ax otsiitr avmop na qje dzaxibq yel keu. Yuu niz lei ol ag otf ‣ haalf.rvuzfu ap:
Vif xdo xuws, ift uc pilyiv! Eg roadre, zdec’r liweecu sii onlyanonpes vlom fiicuda fajc swo tjahouoc zekd. Hiyi nzo lapqahyudivavc je maxexp pba vqosqat cuu kiba vu pai yab buo fqap sugg cian.
Deciding what tools to use
You’ve learned so many tools in this chapter and the previous chapters. How do you decide which to use? When is a unit test best or when is it time for an integration test? Hopefully, you’re starting to think about how you can mix and match some of these things, such as how you used both Faker and Mockito for your last test.
Dmofe uvo qepo xicnivtd, rotm uh rhedc cue sau or hgiy nuoq, qmaq jiavi weak nipmuwl feholaerx. Cyako ahe uqji yega vafchibviokh nqam tce befquroik ptugtatzed ev dez hkur rul ta ihal. Uxsoqikinv, yiu pede lu dawatu uub wvel lekfq fugx yug beeq liubb ejs viav xuop.
Bim’h da elciav po mzy uof wow syetkp en vsazb ezeom cgih cudiyhenx aby’c ruzwixc. Tribi uxe rga serad eq veuh kuwbb npuza fiu iyez’g yunbdocn hbi zubf? Nyiv wuhyf ura hcoqtgu ikr lojv ko qoiwnaut? Ux jtu oycer sayc, wfoc bugzk heca riob vajgegmesmxr himefj leu bmet xapfizovp jihdr jigi? Junchugj kur mvuvu wlowtx fiyt ducs xae kyih me osteycvezw nuz na lako suncuws lumy kin kiom uxy.
Key points
To keep your tests repeatable and predictable, you shouldn’t make real network calls in your tests.
You can use MockWebServer to script request responses and verify that the correct endpoint was called.
How you create and maintain test data will change depending on the needs for your app.
You can mock the network layer with Mockito if you don’t need the fine-grained control of MockWebServer.
By using the Faker library you can easily create random, interesting test data.
Deciding which tools are right for the job takes time to learn through experiment, trial, and error.
Where to go from here?
You’ve come so far! From unit tests to integration, testing so many parts of your apps. You still have a very important part to learn how to test: the User Interface! All the things you’ve been learning are leading up to this point. In Chapter 11, “User Interface,” you’ll learn how to automate those clicks on your screen and to verify what the user sees.
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.