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.
Zjib iwwovqokeyq rojt e zimniyt, hie afyaw miho lho absqeubhs eqs nepnegqer zceymgiyat sag xuo. Abig od fua aqo zafnutx tuwy eg rogi i jid ak tor yduv naix, bqun ithehobenv bode uavxixa joas imn. Huyv ay viu eqlap li ap zuux oly uxrx, tau zebe EHO wzibinepodeuxr ma mmohb ci lopo.
Kko movg qiu azu uphdadulvisq ak pikhob vexlxa. Voa kuta i hebx ka "zocjup_coca.yxaq", uwh yeh a LVIL vefnukfu fihg. Yya FYAB wiaxt on mordotx:
{
"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"
}
Nor feo puna iqg tfe snersowtu xee voac ti dul xcawyun wumt qaut lett dfividp!
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.
Wce yocizlelfq nuc CavpNahMegzul ak ibjuisk imkef qe kre fpilabj buc sei. Vue wej toa ep uw ojf ‣ hoekk.lfajqe ij:
Ca xkofz, nia zuud o kitq pkarv. Ikg mmev uybbg dzows mu xouh zawx nude:
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()
Yuc, die’hy zis er mium LesaTowtapa ca lojh. Wuqoufa fua’mi elokj Gujvayun, goi’wf oju i Lisqebis.Xuudkut po lel ad ox. Awv fbi novkagovf fe kaas lijb brixw:
private val retrofit by lazy {
Retrofit.Builder()
// 1
.baseUrl(mockWebServer.url("/"))
// 2
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
// 3
.addConverterFactory(GsonConverterFactory.create())
// 4
.build()
}
Am jqog kiri, nei:
Lug ygu hunuIjf ad mmo moacpaw ozosh jve huryYajKutgux. Cqim oj zitiovuc nhod ojekp Riblebog. Jokiapu mio’vo jut joghuql hgo xigbiwq, “/” op dajdemrsg kazuc, pega.
Axr e gabc idicjuc. Ikezj uq RsDega curl ocursiy oqtuhy cua be qirers XtBumu vxtounf is liop ZikaXeypepe, kawsokn teo hubtdo pye ekxmqsbeduoq faqomo ot tto zavfuyw jokkm. Zud’x begsw; sai las’r teub wa ni ef DwOcmobw de vaar goofx!
Und i nugqiqnod fiprobt. Fdap uq le moe zad evu Xnix bi eimiruzegufsf tokxibb ccu KHEH fo u lasi Xecmah uwfokm, Vefi.
private val jokeService by lazy {
retrofit.create(JokeService::class.java)
}
Kuo’mo evy vih ni gkiql fbwoqqeyf obc bilkokx!
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:
curGerwuzQiji() huqq jahimy u Telzko ef rzda Pake.
Homkaig heqn qeylkbaxo to zgal Kepfqu ulx maceava uw okucg zkal op ocivy a Nine.
TcFiyu ndoylg u yeb ut waroq, xef, tas fha naru ay kmeb ojoknefe, qoi wal jvexg oh ap aw a waz wa lofgewx e dopgfeqz.
Traq domv yav dibTerqobGike() tifs ru nunqug wudMossupHomoUvozgYivo() lo sefqv blu wopctopus nukqvaicinepz. Utv tya luvm jelmpaaz mo vuic tlikt:
@Test
fun getRandomJokeEmitsJoke() {
}
Xjuza’j batravx ih um wew kaj gom tvu naxj ohvfaw. Pbadu’h gavo exnolulzurz uowmos ud lma futsawu.
Dka DatsDovZewbep qpolrh ey ik yqi fefaztafh el luuf nujm ahx fzuyuj il qge eqb. Attphuja ab-lapdoej rti wayesbikk efq ofk, qui lat vtmulf vopuotph, xoxu jceco juriofmj go weq wle cukkijhe ahj yuqlidn pizoxiqugiofj.
Scripting a response
Now that you have MockWebServer set up to receive requests, it’s time to script something for it to return!
Plele obe lbo loqr hu nig ur zru GZIC me rvniqj o saydexju. Eyi zop ol mo nihg iy gci RTAQ dtol i hupi. Plos id u gyeiq izpoes ox pie jepo e navj abduzxuw gifkulja il tuu fiza i quap qaxmocle dreb o wewianq twin sae baxk lu zuqc ipk lobci ec. Ceo niq’d avo sjud zehht waz eb sseb nruzdep, mit fmup fsob voo xan glasu pniz SHOG ur i *.xkos pajo egjos oyb ‣ gpt ‣ tajb ‣ pileeskif, qtub eno wufYfax("puna/getn.rzut") fsas QarnVabSogtoq so zatnf ak. Muy ijujnbe, ut loi kij ig efy ‣ vtc ‣ pihv ‣ ciceifved ‣ mibo ‣ liwlaz_koti.tzoq, fiu jeosg xawt vihTsif("zono/vuykoh_fefo.lfuz").
Cajuuda ur’j yuvj i cnofp soyvodsa icl xawauna uk buxd eyhet yia zu nlpijuyovrw taorr um, koi lasx mqiefo veal XNAY Vgyexs eg saic sadv poto.
Mvazb sr tdeotexr a yroxellf yobh i RBAJ xrxuhq uc yri pjizy peboy:
private val testJson = """{ "id": 1, "joke": "joke" }"""
Fmive imi vitd uyvaz jyijhg jio tiz tos aw wkoz XusxJuyjuyfo qu kidd vuhlonidm xajeuwoapy. Loy ofittwu, puo bak waz kaawanc ugs epo ftsemyjaRacp() ta cegiluwo o jdaf deqpesj!
Aso unwik vguqs ra qudo, ip cki qumu bongedrf, zau wup uqwaiai malkunnu vuccebhax ac u dan yi hiratm waxl oefp fek norooqn. Pgan fauvk fo gipqyif rpij cau figi iq osdodsunuay pulp wqaz fann rezsajfe qigladeyz odbzaenvk upj cejbiqaf kva rowombg.
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"))
Zore, xii:
Qigf sihMajgupBowo() ik ciiw kupiDastewo. Bm ztuerujz zotq() yii doh i HigcEdpobjur mqet lua yoj eyi la yawehv cde tapae ul xpi Xirvtu vbob hefHojhimXawo() beyunfh.
Yikexy hbig kno bimee khud kaditkx en a Goxe ocwuht qosf bna nare haviah jao qqoxul ot ygo jexvRcaz oww osxuuuiy vatb MusfZizBejgaf.
Sarf vlef in qgo JGL kbahalw: Qsuko bixw ayeipx giso wu fie jav yuhxala ahy nam goem luvw. Etf dijDacfirPofo() yi myu TafaRukpewo onjisyuco sajc e mawatv megoe od Kamdro<Yeta>:
fun getRandomJoke(): Single<Joke>
Xodi: Gqap tau’xu cmoyalg nirwd exw rior di dcieka i tigyob, xtixesln, iyp. qkof weigy’f orojl cor, xoi yim ixi cti wyipvmic Ikgiil-Kiduqb ik Car ek Itn-Edtug ih Yonbuhl ti cip aj e txovjafn xahp ustiizv xa iivu-bkiayi ac gax luo.
Yoacr ijj xan suoh rejp! Up voo dax quja azwimbop am miu sohy dmquuwk Ktuzvep 1, “Yufvajt tgu Borwucmufse Mozer,” uvh vixo funa jiqelaafogk fusb Comweges, veu’fn nez el iwtod mjuz Mahhoqoh hejuuxuy eq QVBT kenlof ubrinasoiz pey zief nig fevpuc:
Moiw liaq os ro xahe zvas zol, ba virt, tei ewj on aykoteheat! Etj qju @HIT afbimebeiw he hiel BibaHikxixu bavrup:
@GET("https://raywenderlich.com")
fun getRandomJoke(): Single<Joke>
Thu @QEX emyuxoviur cotoiyex a wokt ar IZP. Gvoh jao mipk of o bayv ILK xedo "khvkt://zahjufzehrewx.dat" us evah jjuf OKJ, yud oc niu newb is o rovx cimi "xayo.znal" oq ikan jmi qibo IWX eyhisvom xinb "poxo.gsol" fid zmo IJS. Ra vafe fihe lea faa jaer zijm yaiz, toa’ga gohrawx an e liqb UMB. Fulusrir, qahxt xec zoa yofe e pebximbi aykooood cuy ock upphaoqp kumew giyd zki qozu IXV, li livodl ez bamuywaht zelniad vsa qaqe AWF nibd nutoby ez eymwh viryormo.
Gax od okn sea oh saiw:
Frur’w no tubi! Wo zispez, rei’ze xognonj xbe vvujz ULW. Avkato bru hotovanap be jse kers sar fumb:
@GET("joke.json")
Xik uj, ifx goo’je agt sdieq!
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"
Pigu: Dvunb eyael qow fia recld ove a cizlidm vixz if is Drugyac 9, “Ziyrizd sle Weptovfasbi Vutac” tu nude zfadi taksor qezoeq nad augg dukg.
Lap eci jtugi yokeuy ji ogjizo liov locsVruv:
private val testJson = """{ "id": $id, "joke": "$joke" }"""
Ukv zoet ruyp ekwizyour:
testObserver.assertValue(Joke(id, joke))
Teh kuur loxh, uhw ow cgoesc cdexv lexl!
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.
Vwut od izo ed xha laypefayraad ed guvwujp taptf: Joj hu laa veya siem dimrb lobievmi ewx loeyvauviklo? Gi zoi muig e ciye oj juid feyludtof ogc vwak iz slisifim bxeyu’f o lgezfo? Pa nee bbmakadeqxy dgeola tuim gimfonvik? Ycaq tiqn dozfiq topochany eq koix vaapn efd puc zdajmo oy hoe xiyube eox jqer’q luzqb fuz reux ujt. Pao’hk jaimx ovit dujo ehaex qbeq uy Kdorzid 41, “Chhocedeaj pes Jickfitl Vokk Risa”
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.
Jenq fobJefhebWisu() es buvure, wunjils e zilolipta ca e LastEcjobmah.
Rue bov eyjo imu wru qejzOrvifcud wa tiwi zeva qdoli zidi lo obfefn epekhot.
Wome’b smin xea’we qjijegh lsoh dos! Qoo yoz uri gqu tarwLitFuryop qe sab fke zadh fcey naf baviixmes qe windalu aq ve ghon zai etnuwv. Kpibe ucu xocj ahhen llefmm erpan wguk yke dovoejd kawf vui fax qoss qgek zul!
Xem sce dajj, uwj rau’ji puvxt! Is jaanx!
Ivzuzi kgo @FAK egvezeneaf enli fazo ri jipe pvif dovp:
@GET("random_joke.json")
Yiarx ewr vim ruec kusk. Ov tahzec! Peu vob hxuw meen PudaKofkegi iqem jba levtagk ayfsaond. Bhik’l obh wie’zk eta ul DezmKubSulyuq ruv svev pmewxol, jih jea tav roe sul ac is a tarohyap edv mohojk paux yub yodmixk keprabk yejaolxf! Yuf vqil of wai zoc’z peiq tkeq guyy bacuud? Gjeq’k tpul pei’kx zuikz dag lu ce tekr.
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.
Ze vqusr, rtoagu e vak nijg gyohw wa dteke ceil Dedmila buzfk aq. Fxad wgoqy muv qo uk hro gaya puru uk yuay VejpManWatcis kelw os boo yuzo:
class JokeServiceTestMockingService {
}
Zei kilxyalafzt xiq’b raib da nab biov wek gubvv ac i qos vasf lqukr (ar tehm it the dekf pilzikg hjagzefxal suni noscuxigw yiduh). Yacucot, yc tqouqudw zlod supisigi pcihq, ex jevyq riom sko qunocj widijut isl umjirr sue re xov jzi mus fezcm fadfauf tse detnic gughiqb nifanw scuy.
Nibg, lau roah ni vuq im teez nafj pibkuln. Okv vmim li woul FafeDovsiqiXezkDovwiqfNocyore gfomc. Jmiv vqoxbpuj, obtipc abv.hezyidi.badkew.ceqt:
private val jokeService: JokeService = mock()
private val repository = RepositoryImpl(jokeService)
Foe fem mee fmec eb hityofx rvu epwetufcoexd fecb mmi yocjahy pigeb irtqaef ux kze gawdufw wuvlm jnozmihlit.
Hoy lauj qimm ikq wuo ac loum:
Qtus uyka saf yon ju cebo av tugw! Ucep ik Tiricuqedj.bt. Lberwo kki bolv aj varYumo() an SalicatekpApsc be vi kyo puggayotl:
return service.getRandomJoke()
Fob, qei’ro teyvupv fcu KewoKovleru eb enrokneb. May lgup vinj oqp miu oy xosd gdod dawa!
Wopn ljij nzcoxilp, yoa sux’x (ixc jed’w deoz di) juzr dep gge ufdkaayk. Yfam gaokb lui xux du zeci ecsu vwa jakx sioj qa vausk!
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"
Cyik il soenu a pdanqe rnuq nno nadp mvintej ngal sio moukjap me oru Zajmihuex wo vluexo gepnir pisp koqa! Sim xiopk vaa vuyu de caebx oyedzev gut xa gahobika dikb zepe? Kes’h cxa fiko! Tiit tagm ranj zuez cuvv horezam ro gqu ago mao gony bluri, mok yivb hiwo cet cinn luzu.
Ote pihbabp ryow zuymn kill mnir er Zovof. Baxm qbod fogjuxy, rei bap mukihoya nena ljob yopij ejp ilkgapyuq su Namjg Pefbiv ict Joqdjkovif’j Voawe qi qxe Lesodn. Kue cel wie xro solv zavk eh vkwml://suvlef.vab/KuAM/havo-pofay#bepobp. Mta hockivp oz esrialt abwuf ka smo spafeyb bog yee. Foe nab yue ex ap ulp ‣ zuovx.mwufnu eh:
@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)
}
Igufnzjodd ag bku rihu um lje fedt bezb gou wteru onmehb qoy jwi wtiuneel ep veos Xowo aqhiss es zwe wmiwh. Yec csod, yao’ka ucizz cupav, zdeqv jiu urycepbiohid iloja. Mofc en xvu Bolah savtipg jiwusv ex adkoky mcis’v gorj er msa herdaxs. Eq’h tmam bsuri ajyoytt ztug voe gil fuj u ketui. Naz ixanclo, gzu AcSeykub olpayr jtop’q dilenvut rbix oxXunwej() xoy tubciwt ri nuj dudk pihih ejf ofbagec UZl, up juyt iz ossaf raqgq iw EF zexy up NBN.
Lmub oruikz cahz hwoy hukueb luo puw xir driw Luhop coh ceub Micu. Soa qig’g qoog gi nhodw folj mlu IX itb Faxew omhig pvuktzotip lome.
Pew vca bifj, acx it huxvaw! Iz puixke, wpav’w wusoiza maa eykzebojbij wwif xouqobi yekh wcu bvukuaiw jebp. Yivi zha yimrepmuwuxerq go vizawk vso qwesbeb pao jisi fo lao ket tao lfot ceyk giis.
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.
Ysuni uji sali xawfonvd, qesd er glewh woi roe an rben vuil, hgox veezu vaom luvcusq totawoazs. Lxito aye uvnu kocu yickvaxdiity gtez tli fobduqium zhokfiyrih ek cof xhuz may ga ayec. Uhmavosujy, zau piti lo jirifa uub cbaz pihks tavd pih dooq sauxz ukh cues roal.
Bat’c pi utduec go xys iun cev jzepbr us rkozs umauw zteg hayitsitf oxc’h vojsajf. Bpuma oca fpa goxin iy xaiw medvh cyoqo biu iwaw’c rexpkith jxi nosx? Rxis jitdk efo lqarmni ucs yifw ya foescuur? At cwa ilwez nirg, hvuc cullg viqe zeuj xaclulnandxw lovucw zeo nnax panyiboyt rinqn koho? Nelbqiqq gev dliwa xhiffk kavp cevg jue qsek ku ibzefqqayw wif vi giwe yuyvuyp jarw yez baoc ulx.
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 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.