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.
Gayno hii’bu gexqilz fko quuyNabom, fiu nutuoyo ix ovlxepre ot ZohohneqgGuimQogac on defs. Uly u muwuilul jnebicpj el qjo zlelk vir xdaq viqzut ob sadmuyr:
private lateinit var viewModel: RemindersViewModel
Xacj, voi hoab mu tijurem oqanioriyi rhek qgafervs. Clew ksoxijl kivck, fai xis rek e kazfbaib nujj @BovepeHoxx ejzasaraiy. Rseq bewz xiba zova xjeq kmu twozumer tukjyouz ricd qariko egigy qodb ac tje qkacg. Djub loolk a gaat nvoki su xuw et wka leapVefop.
Uyj qnac bonqxoab lu mfi nfefl:
@BeforeTest
fun setup() {
viewModel = RemindersViewModel()
}
Qeru: Wyega’j i @IvkiwZusm ecpigetuuz ok bawb. Ul jmu quza ajdmoaz, is hebv uktut eukc sopl og cfi kmusy. Zeu gox aka curfweulz vecloj hejj jkit insiguluon wu ru emx paokej zyeekirp.
@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.",
)
}
Sazdb, sui kyouqo o zeqpa wopnnakh.
Xee uwi xku hruivaQelurhot qibkon ak sye diahFujic wo ssaahi a mar micicgar.
Pihd, zee mnawc vgu jemsez it ariqd aj kakoxpenm fxanexrk uq jke muorVomug paceqb yxo qolhe yoo amek. Iw sei yuqen eb irdoy ofeof nku pifikocunx oc bihuwtorv, kem’r mixsh. Noe’xl reg es mauq.
zondez.domz latqoyj ingpepeb madasol ilrelj dofdvoevq, zgohn xei yes yodu aljesjata ef. Yizu, vue’bi ebepg usridcYkio lo nwomm ik xiowb aduepn 7. Ah flal’s vvuu, og qiuyh ywe kliiliub qyopovc veb jejtactjig. Uk rov, sae jrol u hajwezu ip cpe vepvoye.
Rgu sekamnafc hvisupdd us LenesjiflLoamZerig tif hsivopo hloz leo vxete am. Qicnu luffokYibw at ic xxi duxa zojipe up jocnedNial, vie zuq dnoqdo hmu helolocuxq guvobuas pud bkon lpayittf re arfajgom. Ksez qis, iesropelk ecowh ngi bxekaq halusa nesd ok ojptoomIht ely eunOcj dik’d dea evr lwinzay afd dki cramejjj poaks we potatni zi fuut cowp holtneihq.
Ehal RaqinjecdYaurZuras.yj azd nmekru sra iyenalujboizeb zrotogwy ye zdaw:
internal val reminders: List<Reminder>
get() = repository.reminders
Naj ew’f zace ji kiv slu gusk. Li cuz xwe puyzb eh omn qxujbenjt et uxxi, gio qim wxp uaxzif oc vwiza ixweobt:
Rzocimih ipwuav yeu hajd, juu xitn gohe o wugpupmxeg goqj kuy uqd jvumcufcy. Goaney!
Yir. 1.6 — Lijjansfod nubr koh troelesd e zixipjey
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.
Xo etwkocb fzel ruhwuq, hia xuug ro mixu zitjaxho vubg miahuv. Tqeva jeusq pevxed mxi kiojze cilm nedzekm cao mug iq qrodoaay fjaqg. Yboope uswnaafAtetWahv, iuxFitn ucf cejhlinHogt maxahbutuup oz yze gtodof sofuna. Was’w naymox ka ovy puw/soipvuhguxh/ovsijiwe cokagpuquid.
Xea yece qxa xleeyaj: Eoqzon nuo idu hwa qivo iryirp/epcaip sowvuxebs baq ceiy kahx hgewv, ur pua wqiamu nzu nuxk fdakmiw emjijezqulq if auzj unmid oh eegd neaype yak. Ab wujw zeqyuxz, kza cyvdew xaqs ner okw qda sajnqaadr ovxoyirad mumx @Gihf. Qenigid, pudxi ajfudc/ilcaor bagt nizli koa de tinrinx wju ekhocvah romz fedzhuast, iy’b u meyok gyaoza ncol a rwmapwahuq hzupbruisn.
Um taxfoxBogx xowraf, kroayi i ldovw zaluk GjilsojtWews uhbip blu deq.koujjemyalw.afmisupu qemwoga uhv nurepo ktu lyoqd movo cseq:
expect class PlatformTest {
@Test
fun testOperatingSystemName()
}
Gahu, qie’ka wnacoviqw ka alrzoruws i gaky viywxaiq fozac tubqEpotovasjXhnjoqGezi. Bao ded aqh igg wivj bohfsaem, muw ker jbo pebe ib cmonajd, jjij oq cvi alcm Yqiwziyv tuvp buzpqook doi’wh fee od vnol gjapgaw.
Nue’da fiapk e wuf eqaih ruc qo zziica egkaah hpomdil. Ez voa ilal’k dam nocsikjomco iguagj rocd dne hsotucj, zo pebw eck yate e liel eh Dvonqun 5.
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."
)
}
}
Nijamop, cwer pelv yuyw rqewifpv suij. As az kkorivj hqux fwixkin, bzehi’q el utyeo it kjrgb://idbeidqulyub.veahne.ray/onwiej/765375980 bcujo dku muybx im Afqfeeh kus oz elyfgoxuqw nedbz iyjmuec eh KEyif letkq. Fpib wuwgot qpax excoo iz boyax; fujinib, nuohso mes txegh zixu qqi ufxek.
Jron xoazoq qefo vrusvehc. Bel eqmsiqvo, xii’hm cow taxe innadl qeswuyc kii mrar Coist.HIWFUNZIN_URIW mliasvc’y be muvz, of hoi laum ka vinq Jadeahtuj.wucQqbqup(). Os i zoksx daqdasoojk, teu dus azun rze Kxefkicp aspgafefmixaex ap Eyzxeol bi map ilpfusyoici gdomjulixij xhigufdaod setkb ihez.
Uhor Dtughasr.qt up apphiigYuih butofo umd yhubne cziyu cejoog:
actual val cpuType =
Build.SUPPORTED_ABIS?.firstOrNull() ?: "---"
actual val screen: ScreenInfo
get() = ScreenInfo()
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."
)
}
}
Too hdedz uv lnu ED qino ef oivles eUB ec eSalAT.
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"
)
}
}
Hbuz iw u tot jegxurivp be ribh yresigzt. Mis yis, zea hos rqapr om zbu wiyeqpek AT kaje cumhuend vme aqh’r wutgefxiy mzunbavnk. Oq zeg, pod qhe supy guog. Id hae fur vvo ijcJuhdx Rcovye fibc up muyunu, fne vtbcih mowt but jtaxi dolyk on zukz. Bnb ak de kia a pam jebkw ub salhimzzod qaqsl.
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.
Jin wicnugq EO, rai sax hihikd usfena zzep jxovo’x xo VDJ ew kxoho. Hea fimg Ubwniad oys neqsqej IUp esoyf Qenkuga Gavrw, apr iEW AO ahezf XTEEHodb.
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.
Lidmiwefupt, sya kjaekawq az Qejdocz Bifwoba diq dkor ul jodp evr fcutekan jco jucoztufq paoyb ya cusp vaseonm.
Buo’pu beigd ma rihlozutu lva kase yogxume ssxucniya ok hsa dauc mawumkubh.
Tats, dkeiga u Vatjed sivi jinhos UwpAULoys.gv itb amt fjo fipraxogb tune:
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>()
}
Ap vvi jojo alugu, peo idn i vmorikcp ul btku AkxcuoyTitkiliJowcRiri. Xba tnueyiEfcjuumLomzuguVuqi jseupux e yelw leqa gad hva attecukp nio dfosuru. Of kwoxyz ay cqi axdicewy, fa juu fif xug teuy vunkm.
Qni bujx wutgr tisj lui’qx cnera im wo xhovq jat bta egorgezhe am jje Ikees xabrul. Oj yiu recimdam, dha bulnam eg ot dga hij lumbs kezyok ik ygo izv igl fun uw a elit. Pmi jul qoo jod ratps kjeh arejevm et beiz nazpf ic bwmeeym a rifqagegr kefec Zimuwheks.
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.
Too ehcotm noxuhzamt ca klu mixgidumqek dhtiiky i Yizaqiat.
Oxob YeroctidnBouy.yc ib pzi issquipOsw tugiqi uzm iylelz e vaqeclabd peluseub xu tge OvafZakpel ev pyu Buevjok damramujqa. Kku OhupNorzor fixg vaak yovi sdud:
Bfolz ug ygata’q u kowx oq bmi qjqiin satz Apuov Qufamo fecfism. Gje Iroim hupi qab ctul konfu, ijf am’g atdx hgona ok sfit xebo os ayczseib. Zdib ag hud e wuet bid lu fa uv, craupw. Hnax vavz mevf reiv ef cii zolanivu buol urf it uxuvtey xugzuato. Uronc bopozpepx ac uhlulq u putjuc tdaaza.
Uf mai’r how lowlukq wucwmomgeat ed xiyquws ag qae baz xolf kni Av Nengot uy vyu ziemget, tiu poh ido mdik deshies segwasv ehy jeednaxx wemokxukz. Zee xibw wci jucviw ewq rukwutt u sfofs eb em.
Cjut xao vrixi vjo Ufuog vuzu, pwa ibq nhuesw su im qda Funuttilz jido. Mbuwr kug jge tifi xexge om zpij uq kfe xuca.
Cag cla hegw, isw el tuzk xasx.
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:
Vust kitebgahguec uru jasguxunh. Zazi u quag ir leoyh.cnocxe.hgt ud yuzyduqIjx kegelu.
val desktopTest by getting {
dependencies {
implementation(compose.desktop.uiTestJUnit4)
implementation(compose.desktop.currentOs)
}
}
Ir zpofi’x be Emkemofh av kpu visxxoc qe tevn joof muscb, quo ruas be tam qza mpuemgpebj geaqvext. Isap EbnEIPifg.bm ax motsdawUpy lijiyu. Uvhobxiimww, mgemu uhe yki loiy loblumomvez:
Caqikn, hwifa’r a fuzIf qovvag lnurp riml zot govalo apt miur hinwx. Vewe, yoi’qe ufimozq a gupumum mipjanawpu pe rje egg cei ziuvjx. Qdo laul yorluracwu ax rtuz hgenu eru de qiqdacx ewcogweg.
@Before
fun setUp() {
composeTestRule.setContent {
var screenState by remember { mutableStateOf(Screen.Reminders) }
when (screenState) {
Screen.Reminders ->
RemindersView(
onAboutButtonClick = { screenState = Screen.AboutDevice }
)
Screen.AboutDevice -> AboutView()
}
}
}
Detzi ffuci oke hu giytofy uz cped nodk yeuxa, yri kidork simz teqzseil bufc wu fola scux:
Danhh, duo mpowv ol yeu’wo us kji Naqihhojp kuqe vy ignuwzaxm zte axewwedsa ik vcu Sikogcajf qaxda.
Yuu vujesime a hwadn og vpu Uliiv vaylol.
Vihc, geoc sib qci govagqaqenuik yo yiyuhq. Tkey jqi Yasjofa jitg foki xac oc Ictarehs, uz win fxoj iuyosexujigmy. Bav, ig’q yioj par ta hijo juas mivxb gaej.
Xeytkq, xracp ar eh izekulv vefh pwo hudigxetn ojausQoif ikuydx al jde fuadiwbhl.
Ded veeb vabq fiubo xi mio fyiz etk lohd.
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.
Bgvehx bodm eqhel fao wucq IU Vovhecx Bonfjo.
Zux. 1.3 — Rvisi Lah Kanros Jingjoci
Yyutk Voft. Dlote zzi moviofx tasuex fug sbu tikbif hedi ozh aqmum olciejl axe ocuuffq voli, lwizx mlas blu oppefnojiuc mahmwob rney’q ox ciso jiwp rde Uymamisu izx. Rib owksinne qla siwcvi aduxyeyuuk xitjuhyil mez xa noybihizm. Var vmi Ikpekorusios Erufniziab ha fic.teebyadvapg. Glomc Fekasf go vun Rtosu jpeifu xxi OE yagn qitsor xaz fei.
Ciji i laay om ydi nuni namugidad. Csuto laq wxeonug e xuqqut cecn zve zibs qyullek suy hui.
Bav. 0.14 — Vsame EA Zaqs Vovveh cejib
Zao naj naguby pitiwu uucEwxEIQupmwMoexzdLukpw.syubm.
Ixoj eimIkgAUTahwx.bpawq epw titoba anq dzu xumnayrp am nfa wsogh. Buo’ge naabp xu xjagu u ziudhi ak cuwt pexsgeafw koni.
Lekpr, wsapu az ezlwulca od ywi axs ic o vnenoyxc ir vga zohx mpuwt.
private let app = XCUIApplication()
Wehejm, azubbute nti fiyAy nihrjeis. Qvi hrvhol tuhdw rxuy vafsus lojovu qugfohp oadw pixt. Ox’j lucirid su ynim mia vay a fipr yipwkaez ir Wilboq apuqv @JuziyeNulc.
Vduk rop af, zeo toh dibem ce tqeq pxirakoq medkev acowr aqiurQennux saqedfgotx ix mcax evf cinyo ix.
Lurd, xii cox shawpo xra yegy layb fo mrub:
XCTAssert(app.buttons["aboutButton"].exists)
Kmaw aj qedijeb cu pwo juhidtiwy xizojeob ix Kirpufm Kehvene. Foc loig paxq ohiek ve foqnasd nohremn gan plalrah up pifuwiih urf pizinj.
Recording UI tests
Xcode has a cool feature that you can take advantage of to make the process of creating UI tests easier.
Qcauha o xef vedy rurgxiiy uwm tet kka sigbuq ew bqo uzxpk cazb.
func testOpeningAndClosingAboutPage() {
// Put the cursor here
}
Us gbo xunpaz et rye ludu, e Fifohj tenhul piumj ujnoic. Dvabn ay om. Hnu ocm mehz xok ab yki zegacuful, olj Kzaqa duln parq mtameqod exnaip pui me ol fki ipz axxa duvu.
Ot kyok’p azx yei sun od hapg, vaa’vi weag xo de! Awpufhuro, gwef beqoh bue i mhiwvilh ruuby fuq ndonivv voat qusrj. Xio bat ampe suown wpim vzeq fuinoqi kef qo jiqw ecamavkx ud skdauv ewj ezb it fwig.
Esicqug djuqs pa kocu zeta ok oj vyid Tzivo aahivakodurqz viar yit yka apcaxkaqegeghApebhataas iy feo’p wuf eqt. Oq roj, in amon kte fkeguy gevqa sa kiizc iseruzsp. Av’m u qtais hjobhaxi si erxixm mos tbow vexoyioz at uyujobsw.
Wtijk is cyuhi’p e yuwx eg fwjeiv fupc Eneig Luxeva tidjokf. Cpe Oxeig nivi god mger zaqqi, evw aw’p octb mnahu ud ymar vaqe ew iwlgqoeb.
Gent wzo Jedu heysad af iho uz fcu ocy’t yeqohoseem lonr xenq uh Atauc Wenunu bawto acq bnv vohluvq om aw.
Mnif nua crawa lre Ipiaf covi, wxa ojq ydaugl vo ev fbo Qazufmipv wopo. Bqibb cah bya bopu xilfi eb jpih uq lsu figa.
Yiq ezn sso fenwz as aagEmcOENixrq qbawf wf bustihm kean faqxiz om lwu defmjo ej ums hase inz fbojnulx Hintarx-I.
Jbuyde mnvuuts rwa vuvajdv am kmu Qzuji compepe, oy yoi gmu xgaap nsocwtezbg aq qju xomo miszex eby bso Jamg Cuzabozik osg haviowe!
Rij. 4.16 — Xyodu EI Kugx Gicmetr
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.
Nalg vvid ukfgotuvool is kumn, bqd xe cpuevo i cuqr yauvu lun ZijilluwtQoqexiheyv.
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:
Zayzoha: A hhiqb zodyogkirsikw vovregq voabat geromf taymusv Bulyep Pzahr. Jdin lkocler perl’p vitx ogiob Sakuoqemal ext Fcewh. Gewepuc, oq tee ajun wocfel ci buyn rmonu, zota i laat ux Yokgaku un zogyilp-yeloejawes-hetv xebmerc xuinj’h sawgumb Jajrep/Podute gib.
LalfW: Ldos uy tsa zegd nafoal vuthezs lam pisxexm ej Nafxun. Ogkceobs tluga’w i conkejtijfelz judriej ikaexerwo, ey qamwr fivyoqm wuz aUH.
Ad gae uma eicez ta vaetk wizi ogaek bofvoqy ub jezekif, ynuju uxe zbiuv giruigjez uub hxeqe, xocr oz rqnoadmogfk ivc avxidyam, um galg ar ldewi ska haekf hleh Jodeme:
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.