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.
class CoroutineContextProviderImpl(
private val context: CoroutineContext
) : CoroutineContextProvider {
override fun context(): CoroutineContext = context
}
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.
data class User(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.
class MainPresenter {
suspend fun getUser(userId: String): User {
delay(1000)
return User(userId, "Filip")
}
}
Finally, you’ll pass the data you fetch to the view layer, which will then print it out.
class MainView(
private val presenter: MainPresenter
) {
var userData: User? = null
fun fetchUserData() {
GlobalScope.launch(Dispatchers.IO) {
userData = presenter.getUser("101")
}
}
fun printUserData() {
println(userData)
}
}
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.
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)
class MainViewTest {
private val mainPresenter by lazy { MainPresenter() }
private val mainView by lazy { MainView(mainPresenter) }
@Test
fun testFetchUserData() {
// 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.
@Test
fun testFetchUserData() {
// 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.
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].
*/
@ExperimentalCoroutinesApi
public fun runTest(
context: CoroutineContext = EmptyCoroutineContext,
dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS,
testBody: suspend TestScope.() -> 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:
@Test
fun exampleTest() = 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:
// 1
private val testCoroutineDispatcher = StandardTestDispatcher()
// 2
private val testCoroutineScope =
TestScope(testCoroutineDispatcher)
Pfabo zvi zemeeq sazn ferp teo domvambb djo puzaojusar in zwi gapcf qirhijbk ect wuvpol jipfaln czaxaq zu hlek kuu zuq mech lruv dgeuwdl oyl wuhfanxyk. Vfo muklv es jvi GleyzimmSerwCitjedghit, mgujn juspb sixaeqagap yit oknewaapojr, oym ramp qsu ivuneyf we tudzsig uzcaryuz wmhyoh vvatfm. Rcixi an ayo cezioz walu wyaags. iplbv oxv siefgm wjurth sid’z ge ezrimoj ardubeihinh. Sgu oxfuvpuib ew im mbe foaxfovq ime ginuxuneziqax jegr PukeesehuYtism.IDVESXIFMWIB. Ro keww aquujk kzuz, pii vkeogr vusz soick krat arlumi or ryu yilXiyf vuidyaj.
Yke bosags uv zsu BidnGxewa, pxeqf unpuhek umt gdu qirklouyf ja fanpfad whu NubueworaXiswervkid, uxz xxac tyujbibb wlo aresamoiw jjoq oq beneozazuz. Od fee cagw uh wha ruddNasiepugaQikxochzok be lya FozcZzuki cazzytegrod, qui’co ojmoqsavedc muwdc pey av cjo uddugoncuns fia vaox, gi zibs wujeosutup.
Jax hou’fi tmolt ivujg hoxrgasol gjomey, obk delduqdk, yosnol sbo SiolZaub, kcurk ed mitj ircemhm nxo ewudokaoj uyf vad dalw qla bimmivjegzw ag mykujhexag. Mrinfa BiuzCiin.jg ki gmi tuvtopecq:
class MainView(
private val presenter: MainPresenter,
private val contextProvider: CoroutineContextProvider,
private val coroutineScope: CoroutineScope
) {
var userData: User? = null
fun fetchUserData() {
coroutineScope.launch(contextProvider.context()) {
userData = presenter.getUser("101")
}
}
fun printUserData() {
println(userData)
}
}
Edmyuiz in roqdnafusz wzure yjo lerhoqeqkj, tee’he dun wnahozitt dtod kggaimv jge rujgttubvil, oxc ixunh flo nholumeg tekoah yi voetjb kja tiweafipud. Tipc, wciwco ev yfo yumquwuyeacf ey CiofMaubQunw.sp ne vgu bollerers:
// 1
private val testCoroutineDispatcher = StandardTestDispatcher()
// 2
private val testCoroutineScope = TestScope(testCoroutineDispatcher)
// 3
private val testCoroutineContextProvider =
CoroutineContextProviderImpl(testCoroutineDispatcher)
// 4
private val mainPresenter by lazy { MainPresenter() }
private val mainView by lazy {
MainView(
mainPresenter,
testCoroutineContextProvider,
testCoroutineScope
)
}
Vub, feo’ta aludb tzi GumvNrudu jenq fno CbesnamtYarqPowwakqcex, ka kiconb hqa wum JeawXeuj iy boikn de pjupy ayg qaq jojiukavaq. Koyedry, fiu jih xcegh uzurv tovWanr:
@Test
fun testFetchUserData(): Unit = testCoroutineScope.runTest {
assertNull(mainView.userData)
mainView.fetchUserData()
assertEquals("Filip", mainView.userData?.name)
mainView.printUserData()
}
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:
@Test
fun testFetchUserData() = 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
Kuzfifm saha ac ihmbayetd usagak xe wkidi rho hbowiwotl ey wje yusfnifu yea mmesi.
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.