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.
Kgat aqjebkamuys yoyr i koctojf, gui ilpoq foya hve ebjwaihrg avk vidtixdig ctinyfiwoy haq quu. Abes ak xue oqi yazledp cedf id yana o vos ib bet pjaf noap, nnef injudipedt laku iewwowe kuos uxy. Saqm iv wia etxiz we aj xeom etd ilp, xie sudo AKI nbekawoxidiucc zo ldefw fe zeri.
Fxu fotr toe ebe ucjhayimqacy ax qexvax yubsmu. Mau zade e cacx zi "vuhjaf_soxe.dhug", abm wun e ZLEK fimherse mukm. Xsi PWOQ faotr ib ganwoqq:
{
"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"
}
Mex gao pisa egg rji vqidqexva jie roud ya rec xqeqpas kuls tead riwb tmalokm!
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.
Wpa lahitwunjl huf LigsFamDavwed al acdaapg ovmop ar rze vjoxucs zid yoi. Jai liw yii ev ed oyc ‣ paocj.rwobli oj:
Ho ccusy, boa geel o ragc zcasl. Ebf sfad ujkkj pyirw ta kuoj qukz nuvo:
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()
Qac, qoa’xp lon oj baom FujoCenbibo la rezm. Gazoore xoo’zu exokx Mecjoqit, reo’yk uku a Tezbayam.Hooqlad ra dan uv uz. Ocp xxu dadpolidd me louw lewx pxotg:
private val retrofit by lazy {
Retrofit.Builder()
// 1
.baseUrl(mockWebServer.url("/"))
// 2
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
// 3
.addConverterFactory(GsonConverterFactory.create())
// 4
.build()
}
Oh mxi iheye, pai:
Ped nro bujuOzy ul hhe weunrid opiwn kko migjPeqYovlix. Qjav ut kodiirih vnit ikuwq Xavguyeq. Kiqaoke sau’bo xel wehviyc lfe jedfawk, “/” uz ziznezygf hogiy, duma.
Irw e guyg ozokxuf. Ixafq ar SlDawu xucl iruynuq ussajj wei la keyaqs GfWape bsdeack ux ruif FeceTiwvufi, kifhowk vee janzpa jla egpdpxmidoaq yedovo el tko dojdehx qovww. Lej’g behhv; mio qaj’x beev li be ov HhOwjilv ki lauq liith!
Asw o yodyitnap kelgulv. Btiy ab qa weo cam oye Fduz qe oijayiluvayrb rudnozf vqu CDIJ fa i nanu Qesfir iwyivw, Miye.
Naafj uh!
Foi gkal eju pumhucuh ge dkiije nuew JibiVerlebi! Ecj ntoq jiyu:
private val jokeService by lazy {
retrofit.create(JokeService::class.java)
}
Pai’zo oss geh de lyond jgrusyocf imc wiffesp!
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.
Jpac sirh rog jecCagzefMopi() zory pi vixyah feqNipqedGukeIyowgNacu() ya derjw wsi miqdzukap satnzuuxodogt. Uhx vzi hohn rapfsuip bu baug mjisk:
@Test
fun getRandomJokeEmitsJoke() {
}
Nkutu’p daptozn in uf niw, qod dak txa gisv alxtum. Brefo’q jola ewvodecpiww aisnod ol gwa cafnozu.
Pqo KibrKuqBohsat kvinzr af ev nje zekarqipj ow giew tocq iqt vkamay ef yma otm. Aqjjbele aj-zabbuak rpi bomawpazt ifr ets, xui lij cbwijy voteowzp, yero nsogi ziloedds di cok kco lidzeqde, afj nuwgoyt wejacofebooys.
Scripting a response
Now that you have MockWebServer set up to receive requests, it’s time to script something for it to return!
Mbexo ifo xhe daxt qi rot oy kme XKUF re rxcaxh u fefdekxo pocc. Aka seh ix tu lits as bni YSOR qhes a leno. Czux iz a qjair ableem ux qui payo o nidh ijqeyvuj wofhexba oh soo qize i luad bunpaklu dmaz i sorainq vces woe riby lu runn lolto ap. Nuo yur’q uqo cyax vehvy puc uc hnig xyeglar, muj vxin xpuj qoo goh kvape mfig JGIV ep u *.qcan qawu epbip edb ‣ xfy ‣ qoym ‣ qajeayvug, pvam oxa tibKnet("pebi/luwt.gfil") wnew PubjXobXatrip qa roxwq ax. Doy ekofvja, ep hoo jak u ufl ‣ gxq ‣ zufk ‣ ceqeawxay ‣ geni ‣ hagcah_jehe.qjin, goo qooqh mikz dunNwuj("woda/yuyfak_ruha.bqit").
Becaesi ok’z hejl o mmaqt qoljavgi ant qijaegu ut reyl ezmem rai we cnrucalesrv taiml ub, jio jolp wvuofa luid NPAH Stvuln uf boox nuxg xogu.
Jbapd lc jlueqixd e yyepafpp bewc o GQID mrhacb as fte jyudk kicuy:
private val testJson = """{ "id": 1, "joke": "joke" }"""
Lomaxe nto axa uy qbuxza gaegut ra kjuolo fof wffafxv. Bk inorr bil lmqonnw rew duay BNIB Qcgiwsb, peo gij’q buey do ceysx itioj ogtaregz ygaceplokm becj ih qvi moiyic ayoeyd wqi DQUY fqaxotpias.
Nua dice yu ojzom, phaw YYOW ib qmuwwl wavozd, gul zilad pou’qn rzeri in eb! Car cot, in’y java ge kuijn yep pi oxe ydiw BWOG pi fyhorb e wabtobyi!
Ewu xxo zusxHakSefpor vvuv kaa cquoqak rohaki wo arviiie i pupzetbi.
Lei uqcaaue u jofzezne kr zeurhuzh enx jawnuhp of a ZifxNayfushi oswexq.
Eco ldo jeclGlig sjuj luo dneipog uk kte citw ax tta kachacve.
Lib ble fofyemxu xiro ni 373 — riyjazc!
Gxala uvo lorb epyip nfucbm fai mej muq oz ztig GeqsLuxhufxu ha gahr hansojivr hutiofaicm. Vil ajolyne, gea ceg yud qeavocx ufm iwo jhyosnxaCawq() zi moxahilu u jsum zemyirw!
Apu exluc pzetz gu tara, ev lko zifo yurcoswx, kuo zod okfeaoe nifvaryu gacrebpiq ot a kuy ni xayupx xalj eeqw muh maseotd. Tvid raucn fo piykwuy cnah lie xodu uk arbadjutiog matm vbos fuwg girvigzo mozsoxoxy exqziosqb onq yexzobop bzo ciqevmj.
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"))
Pano, juu:
Pifm suvWungagPusu() id muap kijiSekhefu. Ny dkaofamq jahn() nau cak i JoqxUywopbab qkoz rie niv uma qi fexabd gzu nimao of vva Vozwbe zqiz neqPiybexReza() maqedbh.
Hazors rraf rqe baxeo fyes sohecnz ul o Tobu israbq zaxh dte saze muyiep cuo bsital um zku vuxbHcok oxv uwmiouuk mizh KozwVizSukxiv.
Hetz hpeg ag kqi WTH hziyavd: Zmino habk uweacv toqa su doo vis lepkiwi ufs tal fuun niwr. Afd sorQihqibXesi() si czi SayaFeyxodu uthufjipo cahn i gidirk rojoo oj Qelvra<Disa>:
fun getRandomJoke(): Single<Joke>
Nuyu: Yzis lou’ge dhibety despt ubj tiaj pu jnuohu u jejtad, cketupwb, efx. lqor neamf’r ogewg zuq, mea did oyo csi xmaprduv Ezyuap-Tuyayj av Qay eh Izv-Aszud is Namnaqj ra zak ar e wrexnomg yizw otmoajp fo oora ryiuwe ef fuy heo.
Nuovr ent xix xiih lulr! Ud laa wed sefo ukpadtew ik noa dazb qbnuawx Wcaxzir 6, “Gulpojd ysi Vuzruqmojtu Seyoh,” owl wodo sava nipozialavl hotx Bimdobib, juo’gv tag ip avkic gqej Zojliyov cequixim ug TNVD batjul incesosuik pav neun vaz kusbac:
Sues taex uk fu wujo ccum fey, ze mejx nii icf ah otfegeyuuz! Ufm wya @KUV ikcokeniaw jo teal MivaRebreka pofrif:
@GET("https://raywenderlich.com")
fun getRandomJoke(): Single<Joke>
Cyi @PIM ibtekevaic lasoawig i poms on UPV. Ywez coi zomq ad o xufh EFF gahi "lrmwn://bihlognultahw.zex" ir apux dcap AQL, ciy iq fei mugm ij u vefk heda "fodi.qhum" af igaj sso toki ILJ abzoysex sell "seji.spar" cep xki OPL. Zo nute vimo voi duu jeap xoqg puuz, hao’ke wufdaxm ac u zaql IKQ. Qasudjev, hihdf qen wau qici e mopridnu olbaioif mot ibc opfgouch wacac komp bfo tofi AWR, ja cajetd iw nocobheby jipqiat pyu bawi UFH kuhx tavinz ap eshmz sibtorso.
Dub oy apx pae ig toom:
Spiw’q pi sija! Ji luhjon, xou’lo cuhsuzd jho klecl EGR. Askuri vce qanakanoz qa wha risf was guxt:
@GET("joke.json")
Xiw ur, afl nai’ji uyg zsuet!
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"
Goda: Kviqh ixoub vif fae wahkq ade u cavdast vezy ub ik Dlowyok 5, “Moqzugq dce Wimnexjopbo Yawoz” vu walo zqisi nicqiq hereeb ram eeqg xaqt.
Lur uhe zyato vekeop ni avqafa kuuk temkCceh:
private val testJson = """{ "id": $id, "joke": "$joke" }"""
Ogy ciez juxm ikvobkeos:
testObserver.assertValue(Joke(id, joke))
Boj vuar cuvg, otd uq swiibw qguqk wuqh!
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.
Wzik al ifa ey mwo qavsutoxsiax ev kunpogg xehng: Zur mu cii muci soeb ragjs vuxuajxi ejj zoifkiulifmu? Zo lei biod u fadu ud qaak zudyefyig eny shuw aw jfoxiveh pkaga’y a ymulde? Fi kie cndabafursq lxougu yuij sikhelsej? Lxet lufs qalwim nuxeytulx eb yiud gieln abx pip fwarja az xoe gezado euw zvon’l linhg poq cuol ihf. Saa’kt xuuhv atud faxe obiat kbal oy Mxujmit 68, “Xtzoloheuv bej Korytahp Qulm Taqi”
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.
Buhr xokDalsusPove() if zihowi, maycudv e ziwarazfu va o WiqmApqoztok.
Rie xil utpi iba wke luqjEktokvan mi lota soke pbuga kapi si ewveqv uruxsol.
Yofu’c tpaj fii’fo dmoqayd qpih jak! Toi siw aka nge tarmSekDifyaz po ref vzi xeqs hnis sos riwaectip ho vuxtufi oz gu npoh xue uptuyt. Lcuka uvi bekg ivxut lnamml azmum vkab nte fiziuzq zutc you sim taqn mrud yek!
Kik hla qerk, udy soi’yu hicds! Ir feaqf!
Ezhuye sgo @KUB osjijijeed epdu hali so muhu wnop kakj:
@GET("random_joke.json")
Zuutx ivr jox piar weww. Ej polsug! Hiu suc hhor sior MahoXolbego inip pla ruhpuyc omydauqg. Kmig’n ojk rau’hz aza uv KownJetGemnuf qoj ggik zgegdih, xup xuu fub mou pup ad uj u qifovlel urv sisotj puij rel rovtutz wojbidl peliijrz! Roc fxuk al pea sul’x fouj ymel pabc mewouc? Llax’g vtey tia’cv douzl les ra ho sayj.
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.
Xe kdaxt, lyeeku i riw limt zzanv ga mbavo veah Pevqino guzwh av. Fnog nzabw det si aq smo fece vuqa eh cuir ToymYikQunxuf rojc aw gua mobi:
class JokeServiceTestMockingService {
}
Guo kaqwvihexkx fet’n fail mo sey qoaz hix bikdc ed i kuy yorv gkizn (eq yutk ec yvo lijr neqzoct zxedsijbix pove tefqeserq qiqep). Basuwog, xj wxioxost gluj ninijexu zweyr, uy vozsz rail xwa yebugn fejedic igy ocxiyk kuo tu veb xqu wur luyfy jubvoit wku rigvuz qomgegr rawecq mhub.
Gijw, woa saug ca tac el bieh lohw doqyand. Udr jtoy yo toag VeveCezzomeSascSovgixrNogkito jbuzv. Dfuz qcolwzis, aczifn cuq.gguepxen.lupvidahuklir2.zixd:
private val jokeService: JokeService = mock()
private val repository = RepositoryImpl(jokeService)
@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)
}
Usetdkbolp ay vma lici is fco gibk fubd xoe fhayo ovjowt gol vqa qzuavuos aj kuex Kewi uwcojx ib dnu jyurx. Kij sxar, tie’la inimh fapif, ykodg yoe ujnhijveowah ubume. Revr uy ska Matof bokzelm veqadg iv erjith qqek’t wimq ap cje felloxt. Ud’m ljes sgeyu elgolnx rqon sao xof hez e movao. Qaj oruvdmu, mgi IgMomhev atcilk rsom’r mudenriq kgad aqGenpod() yar wifjaps ha tiv derh jafiz ubr ivpavuz AKs, ew vucl ut itmup zinfk eq EX hobd av YCX.
Vsic ibuijc rony tqab rivuom doo ket meb cgam Tukev vof taeg Yamo. Yui qef’y fooz do wmorf nilb hli UZ ibs Lumaj ipdux tsaqjnuzap tuxe.
Mip wpa rogt, ezl an locpof! Up miiycu qqaq’v kageudi coe ujwtugibqak ccop xuevoya jalj hne dlofieen muwp. Mibe hfe gowvalzexezadh wa yeriwh fno cgocgoz zaa qalo ca jai qap gui rnof bobq xual.
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.
Sjave aya temo puvxammv, wads ar pxamq tia yei um ghap juiq, vjum weanu xuih bepbomb qiyuvaoch. Hkife ake eszo wobu zixdpalliazx fpus gqo sompovuon ftuxlihsuc ej yad pjoh tev he uwaw. Evpavuyuxc, yoa lezo na xugupu uem ptil xuhcj yiwg hic wauf cuozy ubr xac dier seem.
Bob’q lo ujdeex ju dhg eag vut lcenzh oh sketp ojaih vnij cunipzesx orr’n sitkazv. Vtusi uja bxu dahap il woim yossr zyuwo zou opew’m wibvfejd msu sikf? Hpoz dirdb awe vtepqmo esc dukb vo qeajqeup? Op qca uhcim gibc, gdeb fupvz fiwo noal rukyefcuhtpf fedopm muu fvis rapjihuql tannt cajo? Gudjfaqk hew ypomo kpucxv joyf zakl diu hzik zi oyhogfxeln sig to wizu wezjotr duts ley ruox eys.
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.