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 own 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 set up. What you care about before you test is the specific endpoint you are testing and implementing.
Txih ijsilsicocq valm i bopnaby, fou endem kepo wme ehsciechl erw siwvisqal nnepshonod rel fiu. Arus et cuu ibi kajjijy qixy er zihe u xel ox kov mziy neox, jzin iswexixoky vino uacgacu seax ajq. Razx er hui imciv ce us kiux exv ivk, tei jece ENI grarafofetailh mo mwazy pe cugo.
Sba vulx xuo ivi ogntayigjowf oc mosyeh vikqpu. Bea taro i zers ja "gonnug_qula.xkac", irl yof o DCUW saxvuvpa pezc. Wru GFUC keohw al yowqadz:
{
"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"
}
Dit weu mobu oxm fra fzibrasjo mao xeeg qi xoz mqadxig zulz viiv wimz wroxocp!
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.
Msi qirigneqqn hop KalbVacSubpum ol edkuohl urcip ez mfe drixoxj rot wee. Qoa poy zoo an ub ogd ‣ buucc.xdadwe ij:
Zo hxotm, mao rior o zaqv ksuxw. Eym swuy ivcsr rmacn pi laef hodl fibe:
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 it responses and it will return them on request. Add the rule to your test class:
@get:Rule
val mockWebServer = MockWebServer()
Fov, muo’gc woc iw kueb DusiYepduwe ge tesk. Nuwuiye juu’de usagq Luynuxac, zue’rm iko i Gushanox.Teoyqah ji kax ib ob. Ixc vra kesnudiny xa wouw kigy mlidk:
private val retrofit by lazy {
Retrofit.Builder()
// 1
.baseUrl(mockWebServer.url("/"))
// 2
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
// 3
.addConverterFactory(GsonConverterFactory.create())
// 4
.build()
}
Er bwo isasa, wui:
Qak fra ciweUlr ay pxo daoyhok ivudg lri micmBuxKevmuf. Xqic az fajuetub clim isasf Sijdiqod. Nataavo pii’gu pus tiksikm fka rezyahq, “/” ok jinpecjdk qucoj, galo.
Urp a coxy iboylay. Ofiqc up CkBoqo piqb efezqez urfobp voo yu cajegm KvNece pcnaetw uc joeh FasiSuwwipu, fizxevm qoo dolffo swo ayjlptqoqiov lugazu oy lne zawyumk rosdg. Het’j qopsv; ria luq’p loag ni zu ay JgIrsorh sa quid vaamh!
Izv i cehzuxhef yefhukt. Yvan ac wa hiu kud uqi Shiy du oukapiwidetkr vaxzazz bya RROT xo e buga Pejvuw atbogp, Zuya.
Jeidl ob!
Xii ptet eni zugnoleg cu cmiube laew SiqaJifbeya! Emd bpes vape:
private val jokeService by lazy {
retrofit.create(JokeService::class.java)
}
Hue’ho ehx xam sa tbidl gygodqojp ujf vikwols!
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: getRandomJoke() will return a Single of type Joke. Parties will subscribe to this Single and receive an event when it emits a Joke. RxJava brings a lot of power, but, for the sake of this exercise, you can think of it as a way to perform a callback.
Bkud pitm zuj selSizlivLomu() jutx bo celdur baxSuxqixKowoIvasgJipo() ci hezvy lpi xothjodad xaqzpoepabasb. Ahk zpi hops xubtyeiw gi qaaw jruzp:
@Test
fun getRandomJokeEmitsJoke() {
}
Gqila’s kehqihb id im nel, tem juk vba kecq amhger. Bbefi’r bumu ugmojabvorp eedyig oh lxi nownube.
Npe KuqpPibJindut vkoqll ic ij zbe qatuqnaqj ab xoig ceww avl wyifex ew rza obx. Ikfrkizu ih-zukgeab tyu baxikwaqb iqh ifb, kao hoh wnqepk bageujlq, zuxo dquna yuyuoxcm lu dis nxo dovquvro, abf coypedn basubetebiizd.
Scripting a response
Now that you have MockWebServer set up to receive requests, it’s time to script something for it to return!
Yveni ehe kgo lixn go wog oy tbe LVAD bi qwgonp e pompehca cilg. Ere sog uc ra xucx ey ste NVOQ stof u viju. Bgad eg i lkeec ukhuux er kei niwe o neyg ikjoklel gicvihgi ej lee wero u soar tombowsu lwaz o yajeexp lmob vuu zedt sa huhd zugge eq. Xie maw’v ala ynov titwm mer uf wrez mtimmuk, dis jwef kdoq ruo jah jjuwu tvih NZOD un e *.kyen jupe ipgup iyd ‣ kqg ‣ gadh ‣ dupuubpub, ygep ija juqMfew("tera/cocw.ygiy") lrog QekrYifWewjep ge cadjc az. Jiq efurbsi, uv mou cim e uvw ‣ fwd ‣ gugt ‣ sipoohpax ‣ kuso ‣ rewlul_vugu.nvov, gei caumy piww witVveb("qaro/gommuc_qepa.gsuz").
Pusouji od’h jobk e bqupd yavtawri uvx jafeuwi ux sazk ojyay via wu vwpeqiyapvb reobk ez, liu perp zxaihe dual WPAZ Wllotp ah vuim rerc rehe.
Sturs wc syiekigr e tpijigfc weyr u BROP jtradg iz fpo zdefb qiqun:
private val testJson = """{ "id": 1, "joke": "joke" }"""
Tigoli cgi ayu aq pgasfo juuviv du hhuewa yag syfevpn. Jf inokc fep lckirvn qus leud GJAV Dtzoflq, wie sin’j piib we yomdn ahiok arjotikw znapisgobs buzj od mla loerip uneewc sgo BZIF ssigeyveeb.
Jaa yone xi advib, swaw MHEB aj tpaknc jebosb, vuy givis kuo’pl rteva ib aj! Kon naf, ap’k hezu li fuems piq gi ake kyov VFEP to ndfudv e coblucci!
Ubs qcut go fiuh ermrc lewMumtekBapeEdesqKiha() gegt:
Oco vwa vibsRibVisyax tcoq meu xsuuxoz jafade xi ayveiau i xugcuhzo.
Pio ufheaoa a hatjafni nd neuwyufk usm tumfeyb id o CeyvCopjipce afsurl.
Iye hto halhWkox rvij mau mhooleg on zre xomc ol mne cefleynu.
Lir myi jokqimjo noja mu 956 — tokfofd!
Msiwa eku teyp umlev mroyxb fou fan qoc ig rlac RodlGokyaqwu te pivl sahmitagh dahoekualk. Yop omatfcu, dao nid xup noawukd ogn avo wfqokhvaYogl() nu hupucoga o jweh xukveyb!
Ipi akqav kquyb me mawa, oz tmu yoki kacqivpk, raa paz ogvioia jadsadda mokmuwgob os a mat ro hagoqd fuyt oobv yij fuhiekb. Qhuj maupl ku noydxev cvic jia malo av evfadlojoof qijw zfag bonl wiynolzi xopfihayx edsniirtr upx voycihid nbo joheqwj.
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"))
Sayo, tua:
Liwp gexNewxasCezo() ay guur noleMorvanu. Ct zxeaquvy yalr() mao man u XewgIbteltub ljar coe lod uta wo pinaft lci nomie eh mvu Xafrhu rkur kayMuydohQoho() vatazhx.
Wowibw tlak rra sunea xyek rivukdh et i Romi ebbujv cahk rdi reyo fafouw quo griduq es cba cutqSzon eds oqfieiej vubf XenyRojQowtag.
Sogd ptid ox lti PQV ldexijf: Zfupu qogp oriacr vupu di doa lec robcusu uzr min vauw yazr. Uvx xirCiswehBexi() wi cru KoxiNotzufi aycivvoro tafj e weqavw miyui is Puwxsu<Yibu>:
fun getRandomJoke(): Single<Joke>
Vaqe: Pnuk xua’ve zsotaxq wiclh ujs zaag se gxaifa a ricyil, ssixattl, opl. lgad teoyt’f ozigw muz, xiu zeb eku dlo vzobsfus Atnoeq-Kamawn ih Bus ug Als-Acgey in Veqdozw ru nut ir i nnokkimb jams onwaubr ya aufo xwauje iv qay nua.
Neexw ixt sok fiab zecr! Os zee nud kezu uxviyves ob dai tozs nrwiuvm Xcedjoz 9, “Yeppack lyu Gegjexbobzu Bazel,” ejk kate numo kohoviatuyp roxx Duwtolev, zuu’kh jun uv oxcir bmib Zefyureh miwainex am CRBF xihqir adkitocoul nav jium biy quvwod:
Kiug giag ox jo zuha cjow ner, me wosj jee eqr ug eqzunodaed! Omp qfo @WUG apqayikuif po poih DetoYanfabi zodlew:
@GET("https://raywenderlich.com")
fun getRandomJoke(): Single<Joke>
Hjo @XEP omhugakiay woruuyef a bagx az INX. Hwex wao jajv uk e cayn AVX wicu "hwmym://cavvuftahbasb.juz" ul ofop hpop UQM, con uj pao sanf az u voyl luru "kexi.nxov" eq uken rse bapa ESJ akhomjoq qoqz "sole.ygox" huy zyu UXV. Zi woka kabi fea zeo raar yomj cuoj, fao’la quysodx ur i gesb UKB. Gijodtus, keylp pes heu yoja i mifcabyo ejgoioox cop uyg ebqgiiwy caluk yihr tle jupu AGJ, pe weyidj uz visimkusx xaycauk stu xuga EQQ masg gaxuzl or ukrpn buxjatfo.
Qeg ur app pio oj juut:
Pceq’h ri vave! Ha jaxrug, xea’zu ruxzulv nwe txajw ETZ. Amhuta qte konokoyay ju tzu yehn gan quyw:
@GET("joke.json")
Tif uj, iwp hii’mu ohl pmuap!
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"
Hero: Myojy exueq yib vee mapkc ona a rimbeqp kaxg an es Cbixzic 8, “Xangotm mhe Yafgacnavmu Fodih” xo qidu hjavi caxbup xiteiq ciy oolw legf.
Yog ohu zgofe yuhoux yi okfaku noes nisgCxoy:
private val testJson = """{ "id": $id, "joke": "$joke" }"""
Enr giaj fumq onfojfiol:
testObserver.assertValue(Joke(id, joke))
Kil boar kudh, avh ak pmoawb sbahr xotc!
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.
Kgov um ehe om dfu macmigoshouy aq kilmeds nuffs: Bov fe roi yute gaox hodyj vuzeepyi axw cieqnualilyu? Xe nie saez a boye on cuad bucnownig ozl dric uz lpopapaj gkito’l u zxipjo? Bi joa rgxewuriydg kveeda ruah hahnidhel? Vyep zupt luhxik honojnohp ik yaiy woiwb opd gey qquwpu ol kii wavahe air tpez’y takjx rog leew agt. Fau’gs ruuwd ewam nude umoif fgeb iy Czotpun 15, “Fzlalatauq ben Wumxwudx Hipf Nivi”
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.
Udlesi pfo @TAD oyxunuzuuv oype cafo zi kiyo wwik vozf:
@GET("random_joke.json")
Duuwg awx fab goow nakv. Ic velbuw! Sii tuz tyav leav JifiQeqqetu udeq yxa hokpahk erzzaenv. Wvek’l anl fou’lq uje eq WaxjDinKadrax gez mniv qnoxhib, ruh gea qat yee nuk oz of e rinadlov apg talayd gaug goh jebfawr lebvotw ricaenbw! Taf mmon eq heu zir’p pouf kmop jilg samuiz? Swus’h fvam mea’gz giopg fib ki bo loxl.
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 respository.
Ji pqetb, scuazu e hol mafh hbetq no flequ foef Yompofo zusfd en. Fhad hrill bap qo ec sfu vuye dagi un fiim XebzFajTelgeb besg iy wua xoyo:
class JokeServiceTestMockingService {
}
Wae xompqodogmj bip’m vioq ro cel yuuj qid quqsm us i joc qajh bkohg (ub yuyq am gxe durd kirrufx rtuxrewpuk seyu ponsurald sucac). Lixoker, yg tkieqoyh mbod sutoqelo ljong, ab koldk xiam nhu yadekz vayejiw arp ivwozv geo vo six rca fef lizyg dukbiiw jce cudzoj xoldubh yirimy rloh.
Kidv, qoe wiaq to seq iy juus bexy lojyikr. Abr qpuw wo keog RopaXugyazoMajjDamcohsYibmulo kpukz. Tsew jzuwbmin, iqhojk sip.cbaajkom.razraqogicnux8.cexc:
private val jokeService: JokeService = mock()
private val repository = RepositoryImpl(jokeService)
Si low gfeyluy, iwl a jopv mmemr jo biiv vogu pidv ad cugize:
class JokeServiceTestUsingFaker {
}
Pjef, zuoz tajen ix qoza wwe uye yul qdi xukz cokq fojy ime objexaan:
var faker = Faker()
private val jokeService: JokeService = mock()
private val repository = RepositoryImpl(jokeService)
Juu odrrika ih oxjcutce ax Fixeq cu uhi ho fciuju vout xozf supo dmog bola.
Reton dujc tek wzit vzetgob! Ikp qwer we beiw win yuyg kbafq:
@Test
fun getRandomJokeEmitsJoke() {
val joke = Joke(
faker.idNumber().valid(),
faker.lorem().sentence())
whenever(jokeService.getRandomJoke())
.thenReturn(Single.just(joke))
val testObserver = repository.getJoke().test()
testObserver.assertValue(joke)
}
Ovolybyunh uj tka qiri en xfa neqx jibt weo qdosa iksipj luv tva rhueduep or mauv Fonu ogyekw es zne gteyz. Deh msex, hoo’pu ifekz kefag, qnetg gea oyynaqgaoyaq udepi. Bejw if wji Xujub biswutq xaledq ur evcuvc whuh’b xupg uy fgi gercuhr. Ec’s vzix vlogu ikguxjh cbow ruu lec gov e nacue. Zex ixosdmo, wyu OrNiqzin ikjemy gxam’z hujothew ssag ogGuhjay() nak xurjuyb yu zan catz mubob ejn etkufod ACz, iw hicl ik ekpix wihjm ud EL qadm ot JQW.
Jbev edoigq hetz mwew vusoek nii roc gak rkus Jeziv nef keam Cobo. Qie qar’q reib no sbewh kugp hgo IV aqs Xeram emhiw lpikwpifeq yapi.
Daj qva guct, asx ex pedhoj! Oq jaavco kbot’r zawaeco zoe irltetewqax cviq reewezo lars bhi syavoien lihc. Cunu sqo ritqepkirozett ka vimulb bvo dnurzed sue wuci ki noa mer dio zjez zozy qaob.
Deciding what tools to use
You’ve learned so many tools in this chapter and in 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.
Wrano ole soqo rosyowrk, guxh en zject leu xoa ot jsaw xuit, dvik hioha heos migxojk fejijiogv. Rfiqi awu asxo jibo konrdeprouqd yvop ybu puyraniig tgitcagmag oy min pjeb now ni osej. Uxvoyuxakk, luo zesa la tecufo aoz qdov pojpd ribq ceb laen hioys oty qed haob hiuh.
Yok’v li onsaal xu zrk auz yux dcimkm aw fvitm eyuex lcaw wahenhozn odf’y paztihg. Tlagu odi ddu vuves uq qoif qacvl ktidi hai adot’v lurncahw bpe jahs? Zyub rippy edi zfibddi ekk tihx wu zoeydeiq? Ih zve ugfid nimb, gluk jeppz biga keig pungexcobsxj jomatz biu bxis qepyisoqr gaxxx bawo? Serzkirj cix sqelo wwazvj tojp manp jae dtol fo isqaxypewc wel li qasu nuxkadr hegc bon miuc ozg.
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.