Mock — шта је то, мок-објекти и библиотеке за тестове

Аутор: IT Sectr Објављено: 2026-04-10 Време читања: 8 мин

Mock — је објекат-заменик који имитира понашање реалне компоненте и омогућава проверу интеракције са њом. За разлику од Stub, који једноставно враћа задату вредност, Mock бележи чињеницу позива методе, прослеђене аргументе и број позива. Према подацима Mockito (2024), Mock је најпопуларнији тип Test Double у Java и Kotlin пројектима, који се користи у више од 70% unit-тестова мобилних апликација.

Главно

  • Mock — објекат који проверава интеракцију: које методе су позване, са којим аргументима и колико пута
  • Mockito — најпопуларнија библиотека за креирање Mock у Java и Android пројектима
  • MockK — алтернатива Mockito за Kotlin са изворном подршком за корутине и sealed class
  • Behavior verification — кључна разлика Mock од Stub: Mock проверава понашање, а не стање
  • Over-mocking — главни Anti-Pattern: mock треба да буде само за спољне зависности

Шта је Mock?

Mock — је објекат који креира фрејмворк за mocking (Mockito, MockK, EasyMock), који имитира интерфејс или класу и бележи све позиве својих метода. Програмер поставља очекивања: метода X ће бити позвана са аргументима Y и вратиће Z. Након извршења теста, Mock проверава да ли су се очекивања поклопила са стварним позивима.

Термин потиче из позоришне метафоре Test Doubles: Mock је „имитатор“ који не само да стоји на сцени (као Dummy), већ игра улогу и проверава да ли је интеракција са њим била исправна. Ако тестирани код није позвао методу коју је Mock очекивао, или је позвао са погрешним аргументима — тест пада са поруком о прекршеном очекивању.

Како Mock ради

Mock се креира преко фабрике фрејмворка: mockk<MyInterface>() или Mockito.mock(MyClass.java). Фрејмворк генерише proxy-објекат који пресреће све позиве метода. Сваки позив се упоређује са унапред дефинисаним очекивањима (expectations). Ако позив одговара очекивању — враћа се задата вредност. Ако не — Mock враћа подразумевану вредност или баца изузетак, у зависности од конфигурације.

Када је Mock неопходан

Mock је обавезан када тестирани код интерагује са компонентама које имају споредне ефекте: слање података на сервер, упис у базу података, логирање, аналитика, навигација, приказ системских дијалога. Без Mock-а ове интеракције се не могу проверити без покретања стварне инфраструктуре. Према Google Testing Blog-у, Mock је једини начин да се провери да је апликација заиста послала analytics-догађај без подизања тест сервера.

Mock и Stub: детаљно поређење

Разлика између Mock и Stub је једна од најдискутованијих тема у тестирању. Оба типа замењују стварну зависност, али на суштински различите начине.

КритеријумMockStub
Главно питањеДа ли је метода позвана?Који резултат је враћен?
ВерификацијаПонашања (verify)Стања (assert)
Враћање податакаОпционалноОбавезно
Примерverify(analytics).logEvent("click")assertEquals(5, repository.getCount())
Када користитиСпоредни ефектиВраћање података

Практично правило: Mock или не

Једноставан тест за избор: поставите себи питање — „ако избришем овај ред кода, хоће ли тест пасти?“. Ако тест проверава повратну вредност — потребан је Stub (провера кроз assert). Ако тест проверава да ли је код позвао методу са исправним аргументима — потребан је Mock (провера кроз verify). Ова дихотомија следи из шаблона Command-Query Separation: методе које мењају стање (commands) захтевају Mock; методе које враћају податке (queries) захтевају Stub.

Mockito и MockK: поређење библиотека

Избор између Mockito и MockK је једна од првих одлука при подешавању тест стека Android пројекта на Kotlin. Обе библиотеке обављају исти задатак, али са различитим приступом Kotlin специфичностима.

Mockito: проверена класика

Mockito — де факто стандард за Java пројекте. Верзија 5.x подржава mock-објекте за final класе, статичке методе и конструкторе захваљујући уграђеном MockMaker-у. За Kotlin пројекте, Mockito захтева додатно подешавање: mockito-kotlin проширења за побољшану синтаксу, mockito-inline за final класе. Mockito не подржава Kotlin корутине и suspend функције без додатних адаптера.

MockK: Kotlin-first приступ

MockK је створен посебно за Kotlin. Он изворно подржава корутине (coEvery, coVerify), sealed class, data class, object-синглтоне и extension функције. Синтакса MockK користи DSL са ламбда-блоковима, што изгледа природно у Kotlin коду. MockK такође уме да mock-ује својства (property mocking) без додатног подешавања — ово је важно за Android пројекте који користе LiveData, StateFlow и Delegates.

kotlin
// 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 секунди при пуном прогону тестова у великим пројектима.

Примери Mock-тестова на Kotlin

Размотрићемо три сценарија: тестирање ViewModel са Mock-зависностима, тестирање UseCase са провером позива API и тестирање корутина са coVerify.

Пример 1: ViewModel са Mock-аналитиком

kotlin
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") }
    }
}

Пример 2: UseCase са асинхроном верификацијом

kotlin
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)
    }
}

Пример 3: провера аргумената са ArgumentCaptor

kotlin
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 у мобилном развоју захтева дисциплину. Кршење ових правила претвара тестове у крхку сметњу која се ломи при сваком рефакторингу.

Mock само спољне границе апликације

Чврсто правило: Mock се креира само за зависности које прелазе границу апликације: API-клијенти, базе података, систем датотека, системски сервиси (LocationManager, BluetoothAdapter, Camera). Унутрашње класе апликације — доменске ентитете, Value Object, једноставне алате — не треба замењивати Mock-ом. Њихово понашање се тестира кроз реалне објекте.

Један assert/verify по тесту

Сваки тест треба да садржи тачно једну логичку проверу — или verify (за Mock), или assert (за Stub). Немојте мешати проверу стања и понашања у једном тесту. Ако треба проверити и API позив и резултат — направите два одвојена теста са различитим називима. Ово правило, познато као „један assert по тесту“, потиче од препорука Kent Beck-а (2002).

  • Користите relaxUnitFun = true у MockK за методе које враћају Unit — иначе ће Mock бацити изузетак на неописани позив
  • Ограничите verify само на критичне позиве — не проверавајте сваки getter и setter, то чини тестове крхким
  • Примењујте ArgumentMatchers смислено — any() скрива важне детаље ако је аргумент критичан за пословну логику
  • Не злоупотребљавајте verifyNoMoreInteractions — овај метод чини тест непотребно крутим на било какве промене у продукцијском коду
  • Користите @MockkAnnotations за аутоматску иницијализацију Mock-објеката — смањује boilerplate и побољшава читљивост

Напредне технике Mock-тестирања

Поред основног mocking-а, постоје напредне технике које решавају специфичне задатке у мобилном развоју: тестирање вишенитности, провера стања Flow и делимично mocking реалних објеката.

Partial Mock са spyK

Spy (или partial mock) омогућава креирање објекта који делегира позиве реалној имплементацији, али омогућава преоптерећење појединих метода. У MockK, spyk се креира на основу реалне инстанце класе: val repo = spyk(InMemoryUserRepository()). Позиви за које су постављена очекивања кроз every иду кроз Mock; остали — кроз реални објекат. Spy је посебно користан за тестирање legacy-кода где ињекција зависности још није имплементирана, а треба преоптеретити само једну методу.

Тестирање StateFlow са Turbine

У савременим Android пројектима на Jetpack Compose, ViewModel излаже стање кроз StateFlow. MockK омогућава mock-овање Flow зависности, а библиотека Turbine поједностављује проверу емисије. Класични шаблон: MockK за UseCase који враћа Flow, Turbine за проверу емисије ViewModel-а. Овај стек препоручује документација Android Testing (Google, 2024) за пројекте на Kotlin Coroutines.

kotlin
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 разликује од Mockito?

Mock — је концепт, тип Test Double који проверава понашање. Mockito — је библиотека за креирање Mock-објеката у Java и Android-у. Друге библиотеке: MockK (Kotlin), EasyMock (Java), Cuckoo (iOS).

Како Mock ради са Kotlin корутинама?

За тестирање suspend-функција са Mock-ом користите MockK (coEvery / coVerify) или Mockito са mockito-kotlin. MockK подржава корутине изворно: coEvery дефинише понашање suspend-функције, coVerify проверава њен позив унутар корутине. Сви suspend-позиви морају се извршавати унутар runTest (kotlinx-coroutines-test).

Може ли Mock враћати различите вредности при поновљеним позивима?

Да. У MockK за то се користи returnsMany: every { api.getData() } returnsMany listOf(response1, response2). У Mockito — ланац thenReturn(value1).thenReturn(value2). Ово је корисно за тестирање понашања при узастопним позивима са различитим одговорима.

Како очистити стање Mock-а између тестова?

У MockK користите анотацију @MockK са пољем relaxed = true и позовите clearMocks(mock) у методи @After. У MockitoMockito.reset(mock). Најбоља пракса: креирајте нови Mock за сваки тест кроз @Before да бисте искључили утицај између тестова.

Како Mock обрађује sealed class у Kotlin-у?

MockK исправно ради са sealed class: every { useCase() } returns Result.Success(data). Mockito не подржава sealed class директно, захтевајући заобилазне путеве. Ово је један од разлога зашто се за Kotlin пројекте препоручује MockK уместо Mockito.

Завршни закључци

  • Mock — тип Test Double који проверава понашање (verify), а не стање (assert) зависности
  • Mockito — стандард за Java/Android, MockK — Kotlin-first избор са подршком корутина и sealed class
  • Главно правило: Mock за спољне границе (мрежа, БП, системски сервиси), реални објекти за унутрашње класе
  • Over-mocking — основни Anti-Pattern: прекомерна замена зависности чини тестове крхким и мало корисним
  • Један тест — једна логичка провера: verify за Mock или assert за Stub, али не оба у истом тесту
  • ArgumentCaptor / slot — правилан начин провере аргумената Mock позива уместо слепог any()
  • MockK се препоручује за Kotlin пројекте: coEvery и coVerify изворно раде са корутинама без додатних адаптера

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође