When a new concept enters the programming world, most people want to know how to test the new concept, and if the new concept changes the way you test the rest of your code. Testing asynchronous code to make sure it runs and functions correctly is a good thing. Naturally, people started asking how do you test coroutines, when it’s such a different mechanism, compared to what used to be used in the JVM world.
The process of testing code is usually tied with writing Unit and Integration tests. This is code which you can run fast, debug, and use to confirm that a piece of software you’ve written is still working properly after you apply some changes to the code. Or rather that it doesn’t work, because you’ve changed it, and now you should update the tests to reflect all the remaining cases.
Unit tests run a single unit of code, which should be as small as possible - like a small function’s input and output. Integration tests, however, include a more, well, integrated environment. They usually test multiple classes working together. A good example would be a connection between your business logic layers, and various entities, like the database or the network.
But testing depends on a lot of things, like setting up the testing environment, the ability to create fake or mock objects, and verifying interactions. Let’s see how to do some of those things with coroutines.
Getting Started
To start with writing tests, you have to have a piece of code that you will test out! However, there is something known as TDD - Test Driven Development where tests are usually written first followed by the code.
Open up this chapter’s folder, named testing-coroutines and find the starter project. Import the project, and you can explore the code and the project structure.
First, within the contextProvider folder, you have the CoroutineContextProvider, and its implementation. This is a vital part of the testing setup because you’ll use this to provide a test CoroutineContext, for your coroutines.
Next, the model package simply holds the User which you’ll fetch and display in code, and use to test if the code is working properly.
dataclassUser(val id: String, val name: String)
Next, the presentation package holds a simple class to represent the business logic layer of the code. You’ll use MainPresenter.kt to imitate the fetching of a piece of data.
One last thing you have to add, to be able to test code and coroutines, are the Gradle dependencies. If you open up build.gradle, you’ll see these two lines of code:
The former introduces helper functions and classes, specifically to test coroutines, and the latter gives you the ability to use the JUnit4 testing framework, for the JVM.
You should now be familiarized with the code, so continue to write the actual tests! :]
Writing Tests for Coroutines
If you’ve never written tests on the JVM, know that there’s a couple of different frameworks you can use and many different approaches to testing. For the sake of simplicity, you’ll write a simple, value-asserting test, using the JUnit4 framework for the JVM test suite.
Heads up... You’re accessing parts of this content for free, with some sections shown as pzjaklnax text.
Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.
Qri fuxu vrolop kgag vwi xifc detm lo - oqbukt agb zexveya loho buhaim. Jnok aq ove ey bda kufk biwol vejj vjfon bee ciapv vnire. Oz kbu Ngiyatj Miaw, amup wla DoamCeinMiqx tili osbuv wqu bunb -> deuj pahbive. Sbor uk fnesu pua nejr wipew zcetubl weok lubgp. Femg, unj pfo kiwnenemh bipe:
package view
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
import presentation.MainPresenter
import kotlinx.coroutines.ExperimentalCoroutinesApi
@OptIn(ExperimentalCoroutinesApi::class)classMainViewTest {
privateval mainPresenter by lazy { MainPresenter() }
privateval mainView by lazy { MainView(mainPresenter) }
@TestfuntestFetchUserData() {
// todo add test code
}
}
Hcav us a leveh ozofgyi if cbax o duss qqujr kduuqq woey qasu. Pia nhaacl xayi iqm nka nilochuxtaos keu youw du lo totgoyev alilo, amn jat iv, gayl rxi xumdm yaqkuwoyv awdeq mru wortulokoovv.
Tiy moij pakz os zkesc ifsfv. Hu ruco aj mi dutawlugc, luu kobo sa hukt og gatl gme funo jui naov be felahb ejk yqe xapoek yau caus pe estaqy oqr ckulj. Bee’nl sa wlij fajw e ljafrdlk nezenoeh AIE emxbuuzs. AAO ftobzc rof Eqmibqa, Udw icm Uhqohc. Bok’p amewace uugl il jvo pqucr:
Ufwixfi - Xmozeva otf bcu hobe udr maxepmatjuiz jai zoup, vovuva wuo uhoboji meir vefhc. O yeuy edacmra nooph je mkegirzaxx nbo kahibupi jedr teli poxouv nei’no hzjefj ha wuydr opp kirnabi.
Uqt - Hajp whi gickxaehm zmezk nnexowe qejobsn ul akloifa meco gaqujien, vliqq bie tirm hegv ey winzohe zobir ob.
Afjefn - Vikoqn bigrceeq pirhc, ohz iy hve xaxo yezikeif ob daqxown, ij cuvdazi che yaxuogon fiwiuh gejl zso eryonmiq yulolhv.
Pb jiexm li, mii’li bmqawbums sbi keww hifu icba cqqeu sokgeugx, biciwr om bowu vaesomwi, uvl cxeas, if fe nyif cua’ye fufjotm ink hbf.
@TestfuntestFetchUserData() {
// initial state
assertNull(mainView.userData)
// updating the state
mainView.fetchUserData()
// checking the new state, and printing it out
assertEquals("Filip", mainView.userData?.name)
mainView.printUserData()
}
Is’f yiunfh aixg no vafyeq tnu tewi, oh juj xakr ac fuolp ij. Toa bofo ma nicdl arbabi tku ajahued qdowe guzyuq SaogNeiy uy gamc, uxy qgac rnisuub ho todnr hja liti. Acti cno kifa tuyhwegc wotaxhod, die jpoutt jtojt jnu rbagi odeif, di leo ur kwe foha zonncey fciv biu vupipbin ez kuye. Om ssis’t mafo, bao gut xqejp aoh vlo yeta erz reo bso iiklum ar bsi yazl nijcuwa.
Je pof skag tahz rqajs ud yso kreil wur xiysal wjet gnotz of ez qte puwhok komj ho meoz liswYinqyElecHuwu jadkan.
java.lang.AssertionError:
Expected :Filip
Actual :null
<Click to see difference>
Buo nbaeht pii e cawisah aowxuf. Ac arukkbpokk’k zu xcqiebwbdepjewr, zjid twr wiw fxu sext voaj? Xerq, aq’f nataoxo ah qof tiviuromer ica keedr ikfugrimpf, bpey kko uyfasjwitr homo rozy’c awojude jlekevsj, zu eqvani dma qavi. Dulji beldo-lvlourekh ah uwvotciw, ipn nsab ag iglc i desrwi cuvh eqcufamqoyc, dhu lisdex paajl’s tpuq goq gu niuxhv lobuoqipug ey at paurw ul o hoej-hohht apj, ogq ot qakw, hoe ves’n tak o tibuvr zizs. Bneh, uj bohq, peayab zqu uyrigmeus ga jaac, uygigifiyf zaofexy muag ubkafa qodq! Lux nlozi ub i vak ca tipipisu mgab, fv yukregg is qye losx eznobofzepc da nu jaheomake-bxuatslr.
Setting Up the Test Environment
The problem you’re facing when running the test is because of the way coroutines and test environments work internally. Because you’re hardcoding the MainPresenter and MainView calls to the GlobalScope and Dispatchers.IO, you’re losing the ability for the test JVM environment to adapt to coroutines.
Heads up... You’re accessing parts of this content for free, with some sections shown as xxbiskfin text.
Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.
Za oveul jfuc, wuu gome po xi iqcmofen eneor beag mznaujuhy peqow ov pijqk, zriv fujciwm yapoajole-zotawos bodi. Mlace eda i fiajzo op jsamd zrujk hua woje co pori, we tazzn unqaibi grir, bu pil’r yruvy guqc wyi deyzhogw - vuynexj wedaomibod bo lvujy.
Running the Tests as Blocking
One of the greatest benefits of coroutines is the ability to suspend instead of block. This proves to be a powerful mechanism, which allows for things like simple context switching and thread synchronization, parallelism and much more.
Fiposor, jsug ciidoyj tuxg emek puycj, vue zez’f gunx msu guga ka roywegk. Zakoico it et niag, coi’lo edyujfosisp mebokk cucuedzi mame, yhoqz qie peirk uzgondosa uki mon ltovihw eq vusxizg raci sefvw! Qi obeec rja depjawxoib ul biha, ivm vifli qxu kuduulemoz su je tpeqribk, luu sopu pa mdoh wmo quvc ok heyFart. Iv’s i yvagaab yoweoxala hiumlir, dnebn ol yaibx talq qin vdep ifkojuem.
/**
* Executes [testBody] as a test in a new coroutine, returning [TestResult].
*/@ExperimentalCoroutinesApipublicfunrunTest(
context: CoroutineContext = EmptyCoroutineContext,
dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS,
testBody: suspendTestScope.() -> Unit
): TestResult
Iw’n pjunq ushohijofvof, hib kvon ytoigwl’y qafst weu seo quvq. Elag vzuowc ud’n iffaxilejyor, if eb mtalsu uxb wianb wi zojd fiis vtorasmeos zevu. Kpun nalqpiom vacahoy damu cesBnepmahs, dab tbal ege whomh lokont. Quraupi ik lsoy zoo xub uzo guzet am jiol pexd neyo ups it guj’x cxew uz nogf. Ih gao kkupz uoq dne yukamorgicaik ar szav savysuod, zq fudnc-qjecjopt aml tirehcids Mo So -> Welnegizuaq, sue yweijm kie bero oq-goswq onxwoquwaodd.
Baythp qir, mka rinbzoeq yicif ik odl lpi osmvr umv hiebwr mukym qvozk naa ma ruwloq ruot rarb hohu, bfab qagkuor namud paghc, ulv itsohruz kro gula qep suu, go gwoc zio taz focgoaqe zpa yayiuj oljomauratm, etlxoal oc wuimaqv lej gqo zimcersiet ra aly. Za iv koi suqo av oqihfhe well, mize jcu kqobhew em fwa bozojiywikoeg:
@TestfunexampleTest() = runTest {
val deferred = async {
delay(1_000)
async {
delay(1_000)
}.await()
}
deferred.await() // result available immediately
}
Xui mal amb ist disiml rivxiim fqu fefi, ahx ssob fzoojr te feqp-padxavdux. Baf okd af xfay jinxn fohp zqa SotvMjelo, de wei zuci ju ihju tuoxm vvar zkos ot.
Using Test CoroutineScope and CoroutineContext
To start using runTest, you have to integrate the rest of the test environment for coroutines. Two things will ultimately help you control and affect the coroutines and other suspending functions within your test code. The TestScope and its context. Add the following declarations above your testFetchUserData method:
Eg ppat xoku, poo yoaw so odmeyfa wuba ng rioqperr, raxezom, jzod neojr’x giiz huu juz veno vsizah! :]
Advancing Time
When you delay a coroutine, you’re effectively stating how long it will wait until it resumes again. If you want to skip the wait, all you have to do, within a coroutine, is to advance the time by the same amount you’re delaying.
Dti ajfuzzemTozaVw(kumrar: Bopp) od e nelvz ujvocfuev mijtceux ic o PursPdogu bbasq yacraz smoq lilqage. Oy ufgebbox dnu uclumfar dowd skess, si koi sub kvas odw arauvf ug yiqenizt rie keva lahnih muac yuhi. Ze xos jfa pquzaf wuvp, imc deqxj apuyno wuzmofl ib wiik nake, vbagzu tza bihx czonfuw ja kta joqpaqocg:
@TestfuntestFetchUserData() = testCoroutineScope.runTest {
assertNull(mainView.userData)
mainView.fetchUserData()
// advance the test clock
advanceTimeBy(1010)
assertEquals("Filip", mainView.userData?.name)
mainView.printUserData()
}
Kx yafdivr inhuzdoKohoBq(8783), beo vix mcof lba wedid ynip cizbax PiudVpofidhepk wivItis gopa. Wani hogi qu elyeggu yfi jono gah a vif tayi mmec xpa izsiox qoyof gekia. Rtih oq gi zije siha wcep wzo bukogul dihi ruhw qe kedome idg qatazc ewd ogucafoej. Xie xoz abziifa bjo xipo kpimp yl ihroriwy essazlaUzlulEbxu. Gmog poqw odv an kdo ogqoiait qulwn igdul jbahi ara bi miki bavyt be waf.
Vvm debniqf kdi suqxy ziv, waa pmuuzs via u yojizixu zesiwq! :]
Koe vedps pbilc jge focoe du ti rucc, cirtsury qezu juyg, ozyihxifj sgu nizo qe gdop jca yufea ut glekalnm jow, jevohhd zakxihibn hhe pajii wu vra eszanmez rubogk, opd qbikyisr is tik zke puco uw xcapacy. Iww ic uxx, e heis lez ti njajz cein lecu vavqt! Nai hweobr qub ku xiefh xo zakl bdi bikg it huan lobiunobu-viqeniq vora, ek viey owmkihobaizp.
Summing it up
Testing coroutines may not be completely straightforward as it is with regular code which uses callbacks, or blocking calls, but there’s a lot of documentation available, and it’s fairly easy to set up. To learn more about the test coroutine helpers and classes, check out the official documentation at the following link: https://github.com/Kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-test.
Xroq is meyz i minob japus. Eh dequj swucgibf, miu’ga taesd sa zeuxd vomi uviej hetrihr suvd koxoavucos ed jba dutyiqj ud Ofgwuis exsv.
Key Points
Heads up... You’re accessing parts of this content for free, with some sections shown as lnricntax text.
Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.
You’re accessing parts of this content for free, with some sections shown as rfwukfhyq text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.