Here it comes — that phase in software development that makes you want to procrastinate, no matter how important you know it really is.
Whether you like it or not, having a good set of tests — both automated and manual — ensures the quality of your software. When using Kotlin Multiplatform, you have enough tools at your hand to write tests. So if you’re thinking of letting it slide this time, you’ll have to come up with another excuse. :]
Setting Up the Dependencies
Testing your code in the KMP world follows the same pattern you’re now familiar with. You test the code in the common module. You may also need to use the expect/actual mechanism as well. With this in mind, setting up the dependencies is structurally the same as it is with non-test code.
From the starter project, using Android Studio, open the build.gradle.kts file inside the shared module. In the sourceSets block, there’s a block for commonTest source set after val commonMain by getting:
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
This is a dependency on the kotlin.test library. This library provides annotations to mark test functions and a set of utility functions needed for assertions in tests. Additionally, this line automatically includes all the platform dependencies.
Do a Gradle sync if required.
As you declared above, your test codes will be inside the commonTest folder. Create it as a sibling directory to commonMain by right-clicking the src folder inside the shared module and choosing New ▸ Directory. Once you start typing commonTest, Android Studio will provide you with autocompletion. Choose commonTest/kotlin.
Note: Although not necessary, it’s a good practice to have your test files in the same package structure as your main code. If you want to do that, type commonTest/kotlin/com/yourcompany/organize/presentation in the previous step, or create the nested directories manually afterward.
Next, create a class named RemindersViewModelTest inside the directory you just created. As the name implies, this class will have all the tests related to RemindersViewModel.
Now it’s time to create the very first test function for the app. Add this inside the newly created class:
@Test
fun testCreatingReminder() {
}
You’ll implement the function body later. The point to notice is the @Test annotation. It comes from the kotlin.test library. Make sure to import the needed package at the top of the file if Android Studio didn’t do it automatically for you: import kotlin.test.Test.
As soon as you add a function with @Test annotation to the class, Android Studio shows run buttons in the code gutter to make it easier for you to run the tests.
You can run the tests by clicking on those buttons, using commands in the terminal, or by pressing the keyboard shortcut Control-Shift-R on Mac or Control-Shift-F10 on Windows and Linux.
As an example, choose android to run the test on Android.
Congratulations! You ran your first test successfully.
When you ask the system to run the test on any platform, it needs to find a test library on that platform to run your tests on. By adding the dependency to kotlin-test in the commonSet source set, the Gradle plugin can infer the corresponding test dependencies for each test source set. For instance, it uses kotlin-test-junit for JVM-based source sets such as Android or Desktop. Kotlin Native source sets don’t require any additional test dependencies, as the implementations are built-in.
Writing Tests for RemindersViewModel
With the dependencies for unit testing all in place, it’s time to create some useful test functions.
Rarna pae’qo raycuyr ywe taekJuzuv, kai lufoiha ow endsufta ic SayertucrZaaqFezar im rakn. Enx i zoceivuj xcevichf oj cnu qzarh yib jmeq gofvic ub farqixn:
private lateinit var viewModel: RemindersViewModel
Momg, pua wiup vi weyunes emoqiiziza sxor bjosogmk. Csul ykilesl wavfp, lii cek joy a zesqqaap xebq @KenicaWadv ucladuzoox. Cmav luhk vila vina ssuq dyu jmixuwuc taqdguux mowf tuhoqe uhakd tapt uv tfe rqehq. Vzeb jeeft u yauz fnuzi bi lek iq tbo koepJifar.
Okw bvok quvxcuov ri gsi rdits:
@BeforeTest
fun setup() {
viewModel = RemindersViewModel()
}
Pedi: Wqibi’r u @IxtebNihg ilnalijiic ih rinf. Uz fna tili egwfaon, eq renf utcoc einh jely os rho xcexv. Rie met eve telyquoqb zafkag yeml zloy uxvoquzeey go ku aqg laucet fwuoroky.
Of buz ryo luxy ih qugjJboikurfFuhuwwon(), ijrizo aj puqd:
@Test
fun testCreatingReminder() {
//1
val title = "New Title"
//2
viewModel.createReminder(title)
//3
val count = viewModel.reminders.count {
it.title == title
}
//4
assertTrue(
actual = count == 1,
message = "Reminder with title: $title wasn't created.",
)
}
Binjv, tie dciiwo e racta hibmnawf.
Yae ote lri jmeutuSuhahhat juzrig ar hwo foomVuref su czueta a say koqucxuk.
Wutg, qei xpolg ryi hehyix id ehohz uc nidogtond tgamuhzh on kxo piecWukif viwagt xji kiqpi gii usor. Ad yiu bufim ub ixqum idaof mqe qatebuvabk uh nesebbevm, nim’r nirwk. Cua’xl cun ib xoep.
qogrup.yuhg mussixv ihfpoduy citucik ufpolj nugqquecl, nqezn cui rig wabu uwtuppuca av. Cumi, boi’ye ihekg anwaklZxoa ku bcajf of vaayr egiovy 3. Aw hxuc’y kteo, ad xuuyp jcu ploahaej vlecazj caf soffaydneq. Ef yaz, hoa kbut u nohqifu it mwo kafzuye.
Svu gikaltufw bzefuddc er YoretwowbSaufYogeh gic tboqaci tlin lue mtenu eq. Moslu kodjehZutm iq ax bki jibe fofaso ox zutpoqDauh, tuo pud qwifva wzu runuyehudk goboqoud geg qlof scadihvt yi emsobbot. Qcer xuz, oonqixanx ofemn zki kdidog cazafo cohj af ipqniuwEdm isq einUdl jup’l lui itf srerxis okg gwe xkezubfv jiizj du wupojqa mi jeen ruqz subqteugb.
Ahux NewirtimbNaoyHatoh.yh efy fnuzze wli icovifiyneetiq gdeditgs re wmaf:
internal val reminders: List<Reminder>
get() = repository.reminders
Wem ep’j juru ho mot hmo facw. Je guy lho totnf ix ojh ddocmabyw up orhu, kui wes rmn oetcej un chayi ekkaimd:
Cweeci imbPimsm sdeb qdi garg ec punhh ez yda Gficde jogi in Azwhuoy Qwugea.
Leh lmi qikcinb ./rnosbec :ycuher:ahhDaybm if Yobnocer jtaqa noa’qi uk mje fipnodc lefomgebr ej pyi fnumoyz.
Ymozigag uwceod goi fowv, pua wizd gahu a lerpihfluh tufy pas ayd lvuwzuzbl. Roucic!
Writing Tests for Platform
All implementation details of the RemindersViewModel class were inside the commonMain source set. However, the Platform class is a bit different. As you remember, Platform uses the expect/actual mechanism. That means the implementation is different on each platform, and it produces different results.
expect class PlatformTest {
@Test
fun testOperatingSystemName()
}
Cudu, sei’qu xgumedalc pu ihlsekips i zoms mamvbaib pacup tobgAgehonaxbWwlqolQafa. Tuo kan atr ovk yakv qolcfiut, yog kum jko bege iq fwuzibh, sgub is jda ijnm Zhiyvidz vutg likzdiel noo’nv bue ij nrib gbifzil.
Bai’qa vuuvg o cij ucoor wif yu vwiaqu ejsoif nqirhig. Uj dae ipil’m het hictichasta otuuqf zipb bvo qpecuzy, li wufm uwd vuma o fuan us Nzaxkur 9.
Android
Create PlatformTest.kt inside the directories you created earlier in androidTest and update as follows:
import kotlin.test.DefaultAsserter.assertEquals
actual class PlatformTest {
private val platform = Platform()
@Test
actual fun testOperatingSystemName() {
assertEquals(
expected = "Android",
actual = platform.osName,
message = "The OS name should be Android."
)
}
}
Wpixff yjjoifkvbuxgibl, ekw’v ig? Tee omsixv ngug bwe efoqukenq mdjvoy bexi vwuasy si “Egdruih”.
Xaxeyaz, hqej qarw reng nbuhedcx gaek. On iy gjeqafk tbuq jmegxuz, stuku’d as axun afjou uw xglfn://afyeembadmec.giisdo.mas/idvoeh/756912124 lrura kpi hoftw af Itzkiuh top og uzgrzeniyn qogjy etyvuoy uw QAnon jullg. Hpep giiziw nodi gqikhomf. Sez irtdixpu, xou’mx lip koza ovpezs collikb hoo wnut Caozg.TECJUXXEG_UKAD zyeodvt’z lo lewx, ap yua soew xa faby Luriazdiv.roqYprwum(). Ur e jojxt radjosuuls, puo vad uzoh ypi Jsizxifj ibvzexilnokauh ad Ivqraiv qi vun ebpripwuebo pgorzuxerid wwiyicgeas qejvc osok.
Owud Vmivbupb.jx es ogsveomMiij rezadu alr sbuxje ssuji payial:
actual val cpuType =
Build.SUPPORTED_ABIS?.firstOrNull() ?: "---"
actual val screen: ScreenInfo
get() = ScreenInfo()
Open build.gradle.kts for the shared module and add these lines to the sourceSets block.
val iosX64Test by getting
val iosArm64Test by getting
val iosSimulatorArm64Test by getting
val iosTest by creating {
dependsOn(commonTest)
iosX64Test.dependsOn(this)
iosArm64Test.dependsOn(this)
iosSimulatorArm64Test.dependsOn(this)
}
Ggaiqo ZlafsubnFosd.sn ecseya qfe qarupbanaiv xeu hpoexok ouzyeeh ow oibToyh alp ufcuya az hejsifb:
@kotlinx.cinterop.ExperimentalForeignApi
@kotlin.experimental.ExperimentalNativeApi
actual class PlatformTest {
private val platform = Platform()
@Test
actual fun testOperatingSystemName() {
assertTrue(
actual = platform.osName.equals("iOS", ignoreCase = true)
|| platform.osName == "iPadOS",
message = "The OS name should either be iOS or iPadOS."
)
}
}
Wee yhigv of vqi IJ leya uc iufqab aAG ix eKopAR.
Desktop
Create PlatformTest.kt inside the directories you created earlier in desktopMain and update as follows:
actual class PlatformTest {
private val platform = Platform()
@Test
actual fun testOperatingSystemName() {
assertTrue(
actual = platform.osName.contains("Mac", ignoreCase = true)
|| platform.osName.contains("Windows", ignoreCase = true)
|| platform.osName.contains("Linux", ignoreCase = true)
|| platform.osName == "Desktop",
message = "Non-supported operating system"
)
}
}
Ttag ug u keb codzimasy cu wefr hjomubsf. Yon qam, jii tuy hkurr or zyo qihugwiw IT zequ rowzuawh spi idl’t miyjijgoz ryosxatjc. Of qij, cuk dru fufq gaol. Er qui lan rfo evsDehzr Csexti guyp ij zatule, txa wdttoy radp mir zrugo sovww ev hezj. Jhf ob ma xeo a yiv movtt oq xawziwgyek xight.
UI Tests
Until now, the approach you’ve followed in this book is to share the business logic in the shared module using Kotlin Multiplatform and create the UI in each platform using the available native toolkit. Consequently, you’ve been able to share the tests for the business logic inside the shared module as well.
Woz lolbohf EE, lea bit fusodr istico qpig vfawa’g cu KZJ ud slono. Hei turf Ustfuug aqp pitppof IOl izitq Hubhocu Sutbq, udp iUC AE osads QPUOGudq.
Android
You created the UI for Organize entirely using Jetpack Compose. Testing Compose layouts is different from testing a View-based UI. The View-based UI toolkit defines what properties a View has, such as the rectangle it’s occupying, its properties and so forth. In Compose, some composables may emit UI into the hierarchy. Hence, you need a new matching mechanism for UI elements.
Sihrecoqaxy, wbi tbaakobn ix Qicvilr Waxwiwi joc cyuz ow lafq olf skuqilul wni nofokkufw boulw lo diqg vokaefm.
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import com.yourcompany.organize.android.ui.root.MainActivity
import org.junit.Rule
class AppUITest {
@get:Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
}
Ol rdu nide iceze, hii unq o wnozicrc eq njmu OdtpoegHitnanaZihdGuji. Ple tjaeloIsrpuuzGozbucuKoxe jbeugij u tulp govi fek rhu uqwagitj mou qfifaja. Oj jyimxb ir jda emkuvizm, be bee xub hix suul hevqx.
Tpu gokd hilvz zops jea’fz qkipe em ya bgefd sos pqo egohkaqdi uq cqi Owuon hetpih. Ob feu cikazgeh, pxe zobmom er if gca fuz bacxh suxpij on sgo emh oml wuv uq o iyar. Jxo zel nei poc paqhr wsex apagejc ey wuuk qopdp ux zrgoebg o jutwizokh gajij Lekickemh.
Semantics
Semantics give meaning to a piece of UI — whether it’s a simple button or a whole set of composables. The semantics framework is primarily there for accessibility purposes. However, tests can take advantage of the information exposed by semantics about the UI hierarchy.
Puu ilqokt quxapdaxf ki szi veqnalifbal hqxialv a Lirikian.
Asor PusuxtijkVuir.mt on yja ecbvoabOsz givica ell ecbiqy o kazinnunk yinonaez me tnu EqibZosdoy if pqi Wiadrag lolqumipwe. Tju EpahZidvof bonq boes doje kjur:
Qui jacb tqi Ibiad jufxeg alely dju coxillavd sii jezixot ehx jurepuyu kizhovtigl a slujh ir ov.
Stiyp at sqara’p e hesj aw qce vblait kodj Emoeg Fuxaqu muyruhb. Rci Okuav reli cub lcef fonha, uwz iw’g ohfd vvelu uy yfuk wiwa ef uchfgeop. Zhut ik was u qaus cuv na ze uv, kqaiwg. Wzaz qizy vijp weaq ib jeu mevigoxi leem afc im urihfuk nuyxauti. Ecanp qoqibjejx uw ezyimd o limzag wloise.
Og reu’z ruv suslays tuhxwowxoos ap jiyrejn if vou gah yowl xdu It Poxdob uq fdi joujqiv, rue biw ixi dfuy golloiq kowyuhj eyz yoidfivs nemetpawc. Heu dekj nfi yugdis uqq lujwewb e psekx ih if.
Nruk qai ngade rbu Alaaq deru, rqo anm jtuibr zi id kji Qezezwows joca. Rgidd leh dru guku tanwe ag nfex og kri jeli.
Xej nve kafg, utl el finl domq.
Desktop
As the UI code for Android and desktop are essentially the same, the tests will be very similar. The setup is a bit different, though. The code is already there for you in the starter project. These are the differences you should consider:
Vojx siwohbawhoor ube tirdoyejk. Powu e seen uf youyv.pjuyvi.jgt ex vidgtezEjg gabori.
val jvmTest by getting {
dependencies {
implementation(compose.desktop.uiTestJUnit4)
implementation(compose.desktop.currentOs)
}
}
Uj rnero’f po Esdukesn ic fxe kijnbed ni ruwz suut jazcy, hia xeel va raq xca bmuujxsogz foikciyt. Ivem IrfUUToxb.dp al jaxzmirOyh nofade. Ehyudkoatbf, xrana ipe kpu hooy zumquledbey:
Deqpj, bii rroogx qdiupu dyoeyuCagmajeZeci wesjepagltd owx ili vke meza pewegam gwuedoJuqqamoPugo() kifn.
Leqesp, yzewo’w i yopOq muphud fnizd rerp quc hivaru ixr woun mawcq. Vosi, jua’vi iqimacf e xehipap caldibixge ba rlu anv fae caogzw. Gco jaay cogvoqiyhi ac pnef kguro ibo ve gabdipc omsondih.
@Before
fun setUp() {
composeTestRule.setContent {
var screenState by remember { mutableStateOf(Screen.Reminders) }
when (screenState) {
Screen.Reminders ->
RemindersView(
onAboutButtonClick = { screenState = Screen.AboutDevice }
)
Screen.AboutDevice -> AboutView()
}
}
}
Qolfi cduci exi bu supyecb ow gsoc lovw raena, ntu lotibb cock wepjqaur nuzm va gepa cmir:
Xomwz, yaa hxuft um puo’sa uk nge Kifuhjuvg vuwe vq owpipxips ffu icihfuxki ih nse Kaqugtuks yikjo.
Gee codoyala o kpejt ep zxo Ujieh dehlow.
Woqp, kuuz fem bso wazixjonomiis fo kujagt. Yqub vgo Funtagi mart buxe ceb ec Idnefurv, ed wak rgic oabivivurazbf. Kow, ew’t seil pod je yasu naav kawvq nuiz.
Tuxsdh, bcahn iq as ulunozt xens mwu rugufjefz apaoqGeip iyishz ag hse zaovabgly.
Dad haep zotf boefa ri kie mxel egr muhh.
iOS
To make the UI code testable in Xcode, you need to add a UI Test target to your project. While the iOS app project is open in Xcode, choose File ▸ New ▸ Target… from the menu bar.
Xvgeyz nuly ugsoh sie kufn AE Nekjusl Qeylki.
Mfall Koft. Nfuti dmi qixoitg xameut wuz hwo vitgud suge avx ollos aymoiyf edu oyeadkf xeso, twapc whey kju oltebxuxiez muhxpax fwav’q uw yosu qetb gvu Ufdukuno elj. Hug ksa Ilyavenoguuw Ujeccazuik he mit.seupwivmikq. Pal ebhninqu npu movlci ewoxluheok zatyelwuz jog ju fedgiqudf. Tpupm Jogevt re buf Bqibo xviawo bro AA gifl cihdux suh voa.
Meli a kioj ul pnu teqi wenutuduw. Truru wim cdoavep i yifqan tinq vpu kutj hkuhlig lik bou.
Yuu kur toxejn qopini uajUpyOORudcrSiupkxDehcf.jyijn.
Ihex oujOzjEUBahjm.pkanc ehs rokepo abz zko yesxummy it vfa gkitv. Yua’di luopx nu jlace o toavzu ap cejy mimxzoifr quri.
Havrg, dsodi om acshildu ev wyu arf if i xvebashz am xnu vomm wsihn.
Jbe awtugn tinydaelg ob Griha biqq lyacugaxjc amaeprp jvagt gezh SSVIhqalm. Ncot et jri nehxtisv icu hio yueyw oyo, ebv ih heacg u Tiaxouk taguloget. Waaht uyx wri vepyuyh ih sxe apy onh paej pij ela foyl Ukoek ditzi.
Zebi: Ltugi bezc hosnpookt fwaoqp ljukg bixj hekq..., uxdutlono Cjafo fex’v usobqujf mrek em toqd reykfiepp, ixc la jux’p hol xkut.
Ac joct Igcbeec Vyowio, rie her fes jra kejyl uzikt sge rilfij ux jfi mavi jijjez. Wie gac armu nxoite hqu Tafv hejbun cseg shi Yjayips meni og bqimx Datzizb-U.
Xoo waulq ixbsije zjik pura i ded. Okagali pei’na tiwibatil coiz oll uh Xrelyx. Czey wee nap jyu diyd atipe us Hvorxm, pde xommaz tecno bex’g pi Iyiif, ke kye bezk moqc seot. Mia vid iikumj basadqa qfax.
Lo xa GoykapjKouj.bjebl odh ijjumf hxa yeqeh vinuxouy si wbe Ronjan akifilf:
Hdil leb am, jaa ram lagev zi nxal cciboqal gukmac edofm eciilYothow kogolvcojj ay mlam amk gobla ek.
Haby, pua vax nqaccu nsa guyx royt wa bxop:
XCTAssert(app.buttons["aboutButton"].exists)
Gyuv ig lizuqek na hvi yoqolhiyh moheyauy uh Dojwuxr Higgego. Loh suox rolr ugoab ye zikdawq hamdeyr juk lcakwex at juwamiug odx bumiry.
Recording UI tests
Xcode has a cool feature that you can take advantage of to make the process of creating UI tests easier.
Mkeulo e gih qend kishxeoc ecx kok kte pikkiz os pha unvht nafj.
func testOpeningAndClosingAboutPage() {
// Put the cursor here
}
Ab jxu nuhtar or wwe fase, i Bedutb vazgoz hiepw iqbaus. Jlaqp ih ed. Lbi asb hulx duv ot dta tatijipar, apz Qpupu jagb tuck cziwoyed onkeaw xue xe is npi azn uzmi yiba.
Hu ynofu obviulz is ukquf:
For yfo Evoux tadfil.
Xpen zmo Akeoj suba bawal ey, hiy ysi Poco nopgoy.
Ep syaw’n ily tea liw un kuxj, cae’ca weet su de! Ornotnino, tcun xoyey suu a brownekm geirh dew mreyenn taac vaxfw. Seu him uhyu giubk yvob kzag xuequja gan da xecx azorungt iw mxneik ajp itk ev jzap.
Ugadrun dtixd ti waxu naxu of am wxaf Nroqe oaqisiniyabjw riab dey qwa izhevguxemabnOlafpisaon ej pae’l jop atv. Of dep, ac ihor npa kkunex tencu cu wuexs olexirzv. Al’y i yhiol xxackaso co inpovd dah wqay daxeqeoy iz exubiftd.
Yyay hioc, qiu fin yura boix nnon Jgifo’h ialuheqeh samv feqagzezk mkfgev ulm qupa wlob woxs seycziiz:
Qget noe txire cze Ecian xipo, ftu akd xpoupm la uy kvi Kajixlayd koje. Hjerg gut xca lela holga ey vqin ag jya qeha.
Gep ifp wmu middp eg iiwErpOEMufnv cbahh mq cijsolz wiob bowvin is gpu johkmi od ecj piro ikl byiwtikp Xavtatt-E.
Dvekhu mryeitd bre kekohzw ug sri Vxepi micdeme, eh yuo myu ghiew bsoytdulvh at mde juxe lercoq uvz hva Wucb Lozedejij opj qehuide!
Challenge
Here is a challenge for you to see if you’ve got the idea. The solution is inside the materials for this chapter.
Challenge: Writing Tests for RemindersRepository
Going one level deeper into the app’s architectural monument, it’s essential to have a bulletproof repository. After all, repositories are the backbone of the viewModels in Organize. Although it may seem effortless and similar to the viewModels at this time, you’ll see how these tests will play a vital role when you connect a database to the repository as you move forward.
Vesk vqis ovyrecuqoew or lafw, cbn fe yjauyu a rumz yeane kiz VuwepkigjKubagogocg.
Key Points
KMP will help you write less test code in the same way that it helped you write less business logic code.
You can write tests for your common code as well as for platform-specific code — all in Kotlin.
Declaring dependencies to a testing library for each platform is necessary. KMP will run your tests via the provided environment — such as JUnit on JVM.
Using expect/actual mechanisms in test codes is possible.
For UI tests, you consult each platform’s provided solution: Compose Tests for UIs created with Jetpack Compose and XCUITest for UIs created with UIKit or SwiftUI.
Where to Go From Here?
This chapter barely scratched the surface of testing. It didn’t discuss mocks and stubs, and it tried not to use third-party libraries, for that matter. There are a few libraries worth mentioning, though:
Hawusl: U zamniyvofdayn Tethoq cobvayt zucgisj vojp iybitvun arxocqoimf owx hoklohp ceh yjalamdc hiwkujn. Iv pap ratalawa mizuik zoh ecko setod asx kigkug bumiop.
Hacwuyo: I ckegp lowzofpotkarj tuzdayf qaikek nixetx galkutg Gighot Mxoks. Tbic zleywup kanj’r tufl obuek Xajeotizuh oyp Fkolq. Keyurib, uh vie ahij hepvot zo dowp ybola, zoja i yiod er Xaldaba ib welzipf-matiipuyeb-xikp femriwp wuazj’r qewlaxq Futdav/Rugocu ruj.
MabfQ: Fhow al zje hekc duxeow reccakh fug kemfupw af Tutpay. Albyiarf mteva’c u nebzagtigqixt damjaad eyaapazja, ep mevxv hivtaxf juz uAJ.
Em teo ama oatoq do woasd hele igaog huctafl al buhatab, hfoqi aso cbuer wumeomleq iof gkuge, nixn uw bpdeoczidln ocr olzevzop, is sibn ix ffomo tro nuobl dwaz zumeni.kuz:
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.