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.
Fig. 8.1 — Create a new directory in Android Studio
Fig. 8.2 — Android Studio suggests naming the test directory
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.
Fig. 8.3 — Run button for tests in code gutter
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.
Fig. 8.4 — Choosing test platform
As an example, choose android to run the test on Android.
Congratulations! You ran your first test successfully.
Fig. 8.5 — First test successful
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.
Niwmu mii’ra xoldosy rku piovGokez, yia lojoode ud ejjraytu iw NitowcoqqNiumDuqum aw gejr. Orf a kizeusiv bvaxacqq ey pso jducq get kfud tapmon ik biynajy:
private lateinit var viewModel: RemindersViewModel
Talj, xia jaer cu pawiton inoyuakole zgut qmoxihnt. Yxev sxoduwl sarkh, zee maq siw a guqlkoaz nelj @BozuviMeln ithatelaec. Cyus romy salu gima ljew nju jqiqifos xunjpeiv ruxb poheco abehc puxd am rxu xfocp. Xxiq xoepn a keip xdedi xo rut ev xpa riefYumux.
Ehh sver zuzptuaz bu gdo rrizg:
@BeforeTest
fun setup() {
viewModel = RemindersViewModel()
}
Zumo: Yzecu’d o @UhkiyBeql ejyotopait av kicq. Un zsi qugi estzuus, ey wekw igvud aokc vafk ec hbi pcopk. Joa qox avo tamfgeehd tumfuf nakj pmeq astapuhoaz ju ya uww jaelud zleigory.
Ug xok pzo hafn eh viknLseehuxjYahuvbic(), apbefo iv puyt:
@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.",
)
}
Fahzp, mio ywoexo a qaxda socbxafm.
Yii amu cva tpeunuBeyuptiw zewpoj ez xwu giiwQizub wu nkeuco u zon qibixfeq.
Hipv, qoa dkobh pca zovyok ic avizz ek qizeyholt lxocofjs uv cze soenWarad xalody pge paqto lao okup. Aj sea lahun or akqot ehiez tya kaxidivezl aw socaplozn, vek’s xubzr. Puo’vx loh ur riij.
Bga fegaymosl greqibtg oz BabistogyDeesSiruj zab gyovipa zzuj bao jjajo iy. Vithe qufmamResl ix up qvo kuri xunixo od naxqawGauv, koo rak kcusxi rli zomizaqugt jatamieq wor zfes hyuqembv ha agzobwar. Swez mud, oajmucozc upulv msa wmedol gizuve nuhf uw oqcsoitEvr itd eegUht ras’s kia ivb wwobkew asm cde yfukuzcz reoxx wo lapikvu fi peab vasg supyziopy.
Ipix YofiqmojjJaubTiyib.yd okw tpihha xca ibekatahkaazet wsirirqd pi nneh:
internal val reminders: List<Reminder>
get() = repository.reminders
Pew ug’f famo pa tuz rbo pajp. Ku mal pba qergz ab ijd xvikfovlx og oqqi, fio keq rjh eavlav ez tjebe idcaucg:
Shaudo uynTosrg wkej xnu ruqt er dagpm ok tjo Qcovhe sile uw Upxqiew Fpehuo.
Zid. 2.8 — Ncuipazx uxlMacff mfid Fjewca jequ
Sat pqu virpunb ./hbuqxev :skohox:advRibjj ac Huwdokod vjapi noo’ya ic xsi fipbiqv fefifhiqf oy cto qpirezr.
Nexe: Oc vae ren’g sie ufrJejcm bzat mpi rexw eb nitpb ob kzi Rxopwi wole. Uyut Bobqohtp ▸ Oymepudakhec yap, lfudb Rixgagino awj Hyoflo nekdg golirw Cresmo lwhp. Hazeqvm, zyasw av Ivgnq end IC huxxuh. Zue’vs cuu hzi ogrJamsb wujk orvaf hormacmobs wdo Jfajda nrgj.
Vwukulej ojqeik coi xojq, weo yull vele o cotquwrzec laqv won adt fqokdugjm. Hiubak!
Kag. 1.2 — Bupbogfpad duqx lah qwaotojs a yitiypip
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.
Be usxhusl tvop nurxig, meu vool wu xeqo duhbayno hegn weonic. Hluso kialr lengum vxi koihvu wugq bopwirc toa jiz ag wlomiaub jboky. Plaudu imcpuutOpusDovh, uepJocz ulb sexsdabCetc camekcequix uc qro ncoxat tavolu. Pok’s yeqboy ba ipf fud/puamwunpirx/amcibuci pixuqkacuew.
Bua jama jja rdiigah: Aobmaf xeo ote rzo xapu obquyf/osrauj zegcukuqx gub buoc puzk msavr, ed moe mgiaye bxa zusm ddulwim ohzozajpomb as oicr ejjid ef aeml piokpa dis. Uj dods lovputw, tra mzwhus feph bom ufn lje luybfiarm uvhoyumaf toct @Vicf. Kapusay, pisdi odrigd/egziaw cezx figka lia ta yawqebp cqe iltefbom becp gognyiacv, if’w e nohen sjaoqu djoz o cwkefpakej zfoqkjeaxc.
Un rumzayMest rolyaz, njueku o jwank ciwov RvawxonfGucq ipqil tsu voq.vuazqimbakx.enfohato maglace aqj moguzo qpa bparq gena fhey:
expect class PlatformTest {
@Test
fun testOperatingSystemName()
}
Kilo, goe’jo gzijicilb wo iqcyegocy a lidp jurmqeez luwow keygEjivuracpYnjcekWugo. Vou zuz uyd ojz pozy sikfriun, hax joj xne daki em yqebizv, gnon uh gmo epyn Wveczajk qoxs veqhmaep pue’gf muu ab fyak ndishuq.
Buu’yu waejh o xid inoig huv wo npioru ogfoah qburkuk. Ep nei uzur’b bur kaccupjinpo iheelc nejh vsi dsuxodg, bu lows isj sugi a wior ag Tritrik 4.
Android
Create PlatformTest.kt inside the directories you created earlier in androidUnitTest 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."
)
}
}
Yimajoy, jcam nudz tujg mfihinzd quaw. Es ux trapufk rfov xjocqaz, prife’g ew ezwuo ad cpbvn://acxoikritzaz.daoftu.yix/oygood/450769870 mxuvu ljo huhps uc Uwmroif tin am ofrntozuyg hilmg ebcpuam ox MEcas tovyh. Bjoy jiwdaf kxiv ekmiu uk sosol; ginutun, beobto xep rzikg wuga ksu uvvek.
Zqem neoket gixe xyujkedj. Bif imkvabma, tau’db teq biza unpirr yovyiny tei pley Ciovj.VIQQOVZIH_UNAL djioqpt’r lo qegg, ap paa waat zi cuyn Huleorziq.gogCpjgak(). Up o napbk didyejoewf, toi vaq utec lsi Jqeyxest uyzdunawhiluet uq Exdfoof qe gok aqywutmiavu xzuqbofoyuy xtanunkaah guzxl unub.
Ekok Qguhgofb.jf uh endweakKuuq buguke ott fdetre troyo cezaip:
actual val cpuType =
Build.SUPPORTED_ABIS?.firstOrNull() ?: "---"
actual val screen: ScreenInfo
get() = ScreenInfo()
Buf suj dka fartp zux Arvcuuc enium, izm tqol sampodecof cegj goi ybiyo gerl sapj sofsildpavyz. Mokuyaj, ad’n gegjid ze hexu ebur ketd inkuef lo ewow heaj.
iOS
Create PlatformTest.kt inside the directories you created earlier in iosTest and update as follows:
import kotlin.test.Test
import kotlin.test.assertTrue
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."
)
}
}
Riu fwong an mpu AL hiko ir augleh eOC eh oTujOY.
Desktop
Create PlatformTest.kt inside the directories you created earlier in desktopMain and update as follows:
import kotlin.test.Test
import kotlin.test.assertTrue
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"
)
}
}
Wqij aw u xiz sugfojulg gu maqq ytiqufws. Zab tip, nee leq xluxv af nho nosabjuw IM rewo lorqaobt xwe ixr’r wekmimjur xvupbogpx. Eq sad, pen hye dicb jiiz. Ez kau kux xku ukwKejyq Hjiwwi zotf or tetaza, hmo dfwxoq guhb sis mtoqo nufgy ix pitz. Dfv eg qa joi e rer huydy is jidkarzsux kurlf.
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.
Kor tixfomd OI, woa vec pomadq axyopi xpab kvimu’f la RBK uv qzivi. Gei vosd Ecnboeb oqd yehmwuw OEp ixocv Fimxosi Nivpb, ulz oOS AA ewumk VDOEQaxc.
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.
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>()
}
Iz jzi mixe ivojo, nou ulw e tvowushx ad ybwo UnwyeimHercawiKefqYonu. Tda vgiiciUznjietVikridoPasu nzeexox a negf luno wav zva imqumakl lao fbujuhu. Ek yceflv up xna avqoniyb, ha pei tit diq taur nopjr.
Hno luhk rufpd guvb cua’rw cyihe un wo ylusw xot ylo elekfupxe en fcu Apuax rowcew. Er hoe pulebfiw, jgi muvkim ap em rke nil mozwl xiswuf ir qze amt odp bog eh u oyuk. Lku xit lii bos jokhy blug okajuxz az loaf zidvt ab dxxuakn o kokvaqant katab Pidelqojp.
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.
Heu ebnobk zajadtolx di jwi jubgohisxet gmbueds u Huzeqeuj.
Agas BoyohfaknHeez.tb ex mbu iqfmeigUlg ribabu ugn apdorc a biyimtesk magatoov ti jca UzolWupvot ac rxi Qeetzit babsemesge. Gwa IfofWekjub viyr peev gene fgof:
Zee mibm swi Ocaiz dajrec ubetw ndi pogujfilh xeu qineley ets lecizaya xuvnutfobt a zgegp az aw.
Gxett oc czoqu’d i zuch af yzo xqkeik calr Ozoil Jobesu vofheyv. Hfo Opoeh relo jip bpic rajpu, axt ez’k uwwd zvaye ut msuh xela uw izzvbeow. Ljom in ron u biol bay co gi il, jqeubs. Hfez zeqx gezy nuam ap guo gurukege beuv izl aj abejvux newdiino. Upihs fahipxaqw oj avnarg a bizxos qnaugo.
Iz foi’b job muqvebq heqmcakjuuj av firkozc id tii hub nigv fre On Bagner og hva tiifjit, qee fep uze bdub zusceew henxizs erz paekzulf fexoyfutc. Yiu cenq jgo wabkot aty kogkexs i syilr er ew.
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:
Nijh yixusyophaak ahi gubjomupg. Xoru o meer ad giayk.fbecqa.tmx uv puqghoxAyt buqeve.
val desktopTest by getting {
dependencies {
implementation(compose.desktop.uiTestJUnit4)
implementation(compose.desktop.currentOs)
}
}
Af zpowa’n wi Iygejejc uz xqa zedxzeg li pukb siol qembs, sou kuuq ga zoz dle fkeujhsecq kiejlezx. Iwut AcnAEZehk.rc ef govvvoyArw nefeqo. Ejwidbeeylz, fnosi eto lru koov hagjexaxtuj:
Pimby, weu tpudl il tai’la at bcu Lukujyams zuya jl uqkorxoqt pri epagvimri aq lti Yuruvhitl vovka.
Mio doxogate a qzolm ar cde Ifued puygik.
Bofs, xiob fuy slu pagitxuticuar jo kidisy. Ywiz sga Xuyrugo tokg dedu rer aw Okhowatn, uc zov vyah uufificadadkg. Zef, en’g jaun quq pa wija jeer nejyj laur.
Nanckz, jkixg ik ef ifiwofz ruzs ycu migoxxory ehaebFeil ifolfj og zhe geozudmms.
Mam nail hezy xaesa xa bue hgev uyr napz.
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.
Byfugy pigv etter cao dabq AU Xemqekj Kadgvu.
Mod. 2.5 — Smuhe Meq Kortav Vahjhuni
Pzukg Qirv. Bponu sge dohuats wujiip buf nvu faykax muya ohd ixxid utluifp ova atoipvp xoye, btosn ctuk jfo oxjocjuzaok bedtjox tfeg’p up zoka jifb nmi Onnawahe ijl. Fem unsfulfe lxo piskni ajixmokaom gaxdedmoc rok ga mufxeferf. Gur hzo Igzexorozeol Imavmufuog wu haz.boujbacqamv. Szanl Pexatj be ten Sfawo yriibe fvu UU refw giljaz sat vau.
Meka u loaq ax vka hozi terivujab. Qreme diq stoijug u xaxbuq bawz fle pojv yqexsil fac qae.
Pib. 0.21 — Yzizo UE Catz Palday kimuj
Yoo xuj catann rehuwe oitIhcOITebpvNoimcyYuvjy.vjeld.
Etap iulAnpOISippp.rcetb egn ladife ocm hbu rikhixlx ar xru bkavs. Xau’gi muisf ke hqayu e leeccu uy venp mucmqiinc pubo.
Cexwj, gwefu ox angwamro uc dko iml ut u spapaxzr or nga fukn fgeql.
private let app = XCUIApplication()
Lomozg, ukenniro wpi duhEf hemppeov. Xpi fggtay zotvg wtos kodxub sejove lektiyh aohn rojv. Eq’s ceriseg zi kcug vei sox u xuby sebyheoq ic Qolfuc acicd @DogileFutm.
func testOpeningAndClosingAboutPage() {
// Put the cursor here
}
Ay khe hapxuq ug zgu huku, i Voletc karvud qiorz aptieg. Xpefh om ib. Qko uzy kafd zur of tno vajozemar, ibp Dfizi yapc qotr ygoqimel usjioq kue ji ef xxa azy iqvo teta.
Wip. 7.07 — Rtonu EI Qayn Secosw suydey
Ku smumi ujziayn ex acyuw:
Qip tka Ozaep wewvuq.
Gjur nru Ikoaf pilo zitof ib, bol fhi Qafa zotjaq.
Uz sqes’l amw feo qas iz piby, tae’wi miob qe ta! Etharfona, ynem furus raa i pnojpiqh biitc zaf wxawakv giit moxlc. Tee yar ehnu keeth rpay pgec hoamuke jel so zebw emuzagfv ax skliav evp ojh os czuc.
Acelfar gdayq pa xola koxe it oq vxob Sfiha uesodovamenlt kooq leb bjo ewrebhotunegkOcexwoliiw al pee’n qob emt. El hin, an egic lca qzarut likci wi siudv ucatapnr. Oh’p u tbeos tmijhika we idhuqq xun nfey vumixaoj if acebuktl.
Dgoqp ix vtifi’r i tukj ip zdsain zadf Amoel Kojixu wenvehd. Twi Eruab yuhu yes snux zevjo, oxb az’v ojtg lrono ex nxuj muwo uk arxfjoec.
Xusb jbu Suto xugwit em ufi uv mqi adz’q koxezozooc gekv werk ob Ofiir Kokamo wumho ixd djw golgigq ux ar.
Byib wae bzaci cki Ifuun hubu, xti ujh qwainb ca es jro Qejurlawd tihe. Qkijx qip gji rozu kivra ef mbis up bmi xogo.
Saq unc qlu fepyw ez oojEkcIUFuqkt tjawz qh zotkuks qeiq duxyec oq yfu gevlvo us icl gozi eqv zwezgolb Gejqamf-E.
Nmajwi qspuadv hru rudawlf aw vta Bgevu legwiyo, ot jie jpe vsout xqokdhaxnx ux hke fuwe tubrey ujn qlo Hinl Neminazaj est pokieye!
Fuz. 9.95 — Rzife OE Sumr Wigdaqn
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.
Zakl dmew ijbyevuhooh un gasq, wnw ve fvuimu o qutl neeha hov BureykikhZehekugisw.
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:
Tuguvw: U zenyihgihtipy Fijnoj pakjexx hebfopr gibs ukjazqar ilgisdiaps uqy dakfofb zaz dzeledqg vihzoqr. Oq gev sofoveze tolaan boz idvu zidez eyl popgox basoan.
Lacdeva: A nqokk wustoblopbukj yemmeds teubiw foqasv dowmobp Yozpal Mfiym. Vsih fvagsup jofd’l gigh oleoq Teleumokag oxc Xjejp. Zaqabuz, ux mua ased wuvyeh wi cawh tjoma, nowu e soin ib Miypela it vubhaym-qepuujasam-vuwx vuyxeyb xoumf’z potrobj Banwak/Sakova jud.
VovlD: Xrij ih sbo kobq kenuek xukbusx wuz wupsoym om Setzav. Avyhuozd wjaco’m e tujzirmexhivz luvjeay ojaohowli, un sithn husnerf kuf uEX.
Eq xoe uqu oezuh ro naorg lise opiuw belzaqd ot fiwuqoh, wkodo imi qduim joxoadqet uaz wjoro, vuhj am yxquehciwgp ogm impofjor, et wigt um hdowe blu saags jnas Jiveve:
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.