Mock — је објекат-заменик који имитира понашање реалне компоненте и омогућава проверу интеракције са њом. За разлику од Stub, који једноставно враћа задату вредност, Mock бележи чињеницу позива методе, прослеђене аргументе и број позива. Према подацима Mockito (2024), Mock је најпопуларнији тип Test Double у Java и Kotlin пројектима, који се користи у више од 70% unit-тестова мобилних апликација.
Главно
Mock — је објекат који креира фрејмворк за mocking (Mockito, MockK, EasyMock), који имитира интерфејс или класу и бележи све позиве својих метода. Програмер поставља очекивања: метода X ће бити позвана са аргументима Y и вратиће Z. Након извршења теста, Mock проверава да ли су се очекивања поклопила са стварним позивима.
Термин потиче из позоришне метафоре Test Doubles: Mock је „имитатор“ који не само да стоји на сцени (као Dummy), већ игра улогу и проверава да ли је интеракција са њим била исправна. Ако тестирани код није позвао методу коју је Mock очекивао, или је позвао са погрешним аргументима — тест пада са поруком о прекршеном очекивању.
Mock се креира преко фабрике фрејмворка: mockk<MyInterface>() или Mockito.mock(MyClass.java). Фрејмворк генерише proxy-објекат који пресреће све позиве метода. Сваки позив се упоређује са унапред дефинисаним очекивањима (expectations). Ако позив одговара очекивању — враћа се задата вредност. Ако не — Mock враћа подразумевану вредност или баца изузетак, у зависности од конфигурације.
Mock је обавезан када тестирани код интерагује са компонентама које имају споредне ефекте: слање података на сервер, упис у базу података, логирање, аналитика, навигација, приказ системских дијалога. Без Mock-а ове интеракције се не могу проверити без покретања стварне инфраструктуре. Према Google Testing Blog-у, Mock је једини начин да се провери да је апликација заиста послала analytics-догађај без подизања тест сервера.
Разлика између Mock и Stub је једна од најдискутованијих тема у тестирању. Оба типа замењују стварну зависност, али на суштински различите начине.
| Критеријум | Mock | Stub |
|---|---|---|
| Главно питање | Да ли је метода позвана? | Који резултат је враћен? |
| Верификација | Понашања (verify) | Стања (assert) |
| Враћање података | Опционално | Обавезно |
| Пример | verify(analytics).logEvent("click") | assertEquals(5, repository.getCount()) |
| Када користити | Споредни ефекти | Враћање података |
Једноставан тест за избор: поставите себи питање — „ако избришем овај ред кода, хоће ли тест пасти?“. Ако тест проверава повратну вредност — потребан је Stub (провера кроз assert). Ако тест проверава да ли је код позвао методу са исправним аргументима — потребан је Mock (провера кроз verify). Ова дихотомија следи из шаблона Command-Query Separation: методе које мењају стање (commands) захтевају Mock; методе које враћају податке (queries) захтевају Stub.
Избор између Mockito и MockK је једна од првих одлука при подешавању тест стека Android пројекта на Kotlin. Обе библиотеке обављају исти задатак, али са различитим приступом Kotlin специфичностима.
Mockito — де факто стандард за Java пројекте. Верзија 5.x подржава mock-објекте за final класе, статичке методе и конструкторе захваљујући уграђеном MockMaker-у. За Kotlin пројекте, Mockito захтева додатно подешавање: mockito-kotlin проширења за побољшану синтаксу, mockito-inline за final класе. Mockito не подржава Kotlin корутине и suspend функције без додатних адаптера.
MockK је створен посебно за Kotlin. Он изворно подржава корутине (coEvery, coVerify), sealed class, data class, object-синглтоне и extension функције. Синтакса MockK користи DSL са ламбда-блоковима, што изгледа природно у Kotlin коду. MockK такође уме да mock-ује својства (property mocking) без додатног подешавања — ово је важно за Android пројекте који користе LiveData, StateFlow и Delegates.
// Mockito + mockito-kotlin
val repository = mock<UserRepository>()
whenever(repository.getUser(1)).thenReturn(user)
// MockK
val repository = mockk<UserRepository>()
every { repository.getUser(1) } returns user
Бенчмаркови (JVM Benchmark, 2024) показују да MockK креира mock-објекте 15–20% брже од Mockito за Kotlin пројекте захваљујући директном раду са bytecode-ом Kotlin, а не Java Reflections. За пројекте са хиљадама unit-тестова разлика у времену изградње може бити приметна: MockK штеди 30–60 секунди при пуном прогону тестова у великим пројектима.
Размотрићемо три сценарија: тестирање ViewModel са Mock-зависностима, тестирање UseCase са провером позива API и тестирање корутина са coVerify.
class ProfileViewModelTest {
private val analytics = mockk<AnalyticsService>()
private val repo = mockk<UserRepository>()
private val vm = ProfileViewModel(repo, analytics)
fun `profile opened logs analytics event`() {
every { analytics.logEvent("profile_opened") } returns Unit
vm.onViewCreated()
verify { analytics.logEvent("profile_opened") }
}
}
class SendMessageUseCaseTest {
private val api = mockk<MessagingApi>()
private val useCase = SendMessageUseCase(api)
fun `send message with correct payload`() = runTest {
val message = Message(text = "Hello", userId = 42)
coEvery { api.sendMessage(any()) } returns MessageResult.Sent("msg_1")
val result = useCase.execute(message)
coVerify {
api.sendMessage(match {
it.text == "Hello" && it.userId == 42
})
}
assertTrue(result is MessageResult.Sent)
}
}
class OrderUseCaseTest {
private val api = mockk<OrderApi>()
private val useCase = OrderUseCase(api)
private val slot = slot<OrderRequest>()
fun `order request contains correct items`() = runTest {
coEvery { api.placeOrder(capture(slot)) } returns OrderResult.Placed("order_1")
useCase.execute(listOf("item_a", "item_b"))
assertEquals(2, slot.captured.items.size)
assertEquals("item_a", slot.captured.items[0])
}
}
Ефикасно коришћење Mock у мобилном развоју захтева дисциплину. Кршење ових правила претвара тестове у крхку сметњу која се ломи при сваком рефакторингу.
Чврсто правило: Mock се креира само за зависности које прелазе границу апликације: API-клијенти, базе података, систем датотека, системски сервиси (LocationManager, BluetoothAdapter, Camera). Унутрашње класе апликације — доменске ентитете, Value Object, једноставне алате — не треба замењивати Mock-ом. Њихово понашање се тестира кроз реалне објекте.
Сваки тест треба да садржи тачно једну логичку проверу — или verify (за Mock), или assert (за Stub). Немојте мешати проверу стања и понашања у једном тесту. Ако треба проверити и API позив и резултат — направите два одвојена теста са различитим називима. Ово правило, познато као „један assert по тесту“, потиче од препорука Kent Beck-а (2002).
Поред основног mocking-а, постоје напредне технике које решавају специфичне задатке у мобилном развоју: тестирање вишенитности, провера стања Flow и делимично mocking реалних објеката.
Spy (или partial mock) омогућава креирање објекта који делегира позиве реалној имплементацији, али омогућава преоптерећење појединих метода. У MockK, spyk се креира на основу реалне инстанце класе: val repo = spyk(InMemoryUserRepository()). Позиви за које су постављена очекивања кроз every иду кроз Mock; остали — кроз реални објекат. Spy је посебно користан за тестирање legacy-кода где ињекција зависности још није имплементирана, а треба преоптеретити само једну методу.
У савременим Android пројектима на Jetpack Compose, ViewModel излаже стање кроз StateFlow. MockK омогућава mock-овање Flow зависности, а библиотека Turbine поједностављује проверу емисије. Класични шаблон: MockK за UseCase који враћа Flow, Turbine за проверу емисије ViewModel-а. Овај стек препоручује документација Android Testing (Google, 2024) за пројекте на Kotlin Coroutines.
class SearchViewModelTest {
private val searchUseCase = mockk<SearchUseCase>()
private val vm = SearchViewModel(searchUseCase)
fun `search emits results`() = runTest {
coEvery { searchUseCase.search("android") } returns
flowOf(SearchResult.Success(listOf(Item("Android TDD"))))
vm.search("android")
vm.state.test {
val state = awaitItem()
assertTrue(state.items.isNotEmpty())
cancelAndIgnoreRemainingEvents()
}
}
}
Често постављана питања
Mock — је концепт, тип Test Double који проверава понашање. Mockito — је библиотека за креирање Mock-објеката у Java и Android-у. Друге библиотеке: MockK (Kotlin), EasyMock (Java), Cuckoo (iOS).
За тестирање suspend-функција са Mock-ом користите MockK (coEvery / coVerify) или Mockito са mockito-kotlin. MockK подржава корутине изворно: coEvery дефинише понашање suspend-функције, coVerify проверава њен позив унутар корутине. Сви suspend-позиви морају се извршавати унутар runTest (kotlinx-coroutines-test).
Да. У MockK за то се користи returnsMany: every { api.getData() } returnsMany listOf(response1, response2). У Mockito — ланац thenReturn(value1).thenReturn(value2). Ово је корисно за тестирање понашања при узастопним позивима са различитим одговорима.
У MockK користите анотацију @MockK са пољем relaxed = true и позовите clearMocks(mock) у методи @After. У Mockito — Mockito.reset(mock). Најбоља пракса: креирајте нови Mock за сваки тест кроз @Before да бисте искључили утицај између тестова.
MockK исправно ради са sealed class: every { useCase() } returns Result.Success(data). Mockito не подржава sealed class директно, захтевајући заобилазне путеве. Ово је један од разлога зашто се за Kotlin пројекте препоручује MockK уместо Mockito.
Завршни закључци
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође