Mock — ay isang kapalit na bagay na ginagaya ang pag-uugali ng isang tunay na component at nagbibigay-daan upang suriin ang interaksyon dito. Hindi tulad ng Stub na simpleng nagbabalik ng isang tinukoy na halaga, itinatala ng Mock ang katotohanan ng pagtawag ng method, ang mga ipinasa na argumento at ang bilang ng mga tawag. Ayon sa datos ng Mockito (2024), ang Mock ay ang pinakasikat na uri ng Test Double sa mga Java at Kotlin na proyekto, na ginagamit sa higit sa 70% ng unit-test ng mga mobile application.
Mga Pangunahing Punto
Mock — ay isang bagay na ginawa ng mocking framework (Mockito, MockK, EasyMock), na ginagaya ang isang interface o klase at itinatala ang lahat ng tawag sa mga method nito. Ang developer ay nagtatakda ng mga inaasahan: ang method X ay tatawagin na may mga argumento Y at magbabalik ng Z. Pagkatapos isagawa ang test, sinusuri ng Mock kung ang mga inaasahan ay tumutugma sa mga aktwal na tawag.
Ang termino ay nagmula sa teatrikal na metapora ng Test Doubles: ang Mock ay isang “tagagaya” na hindi lamang nakatayo sa entablado (tulad ng Dummy), kundi gumaganap ng isang papel at sinusuri kung tama ang interaksyon dito. Kung ang code na sinusuri ay hindi tumawag sa method na inaasahan ng Mock, o tinawag ito na may maling mga argumento — ang test ay nabibigo na may mensahe tungkol sa nilabag na inaasahan.
Ang Mock ay nilikha sa pamamagitan ng pabrika ng framework: mockk<MyInterface>() o Mockito.mock(MyClass.java). Ang framework ay lumilikha ng proxy-object na humahadlang sa lahat ng tawag sa method. Ang bawat tawag ay inihambing sa mga paunang itinakda na inaasahan (expectations). Kung ang tawag ay tumutugma sa inaasahan — ang itinakdang halaga ay ibinabalik. Kung hindi — ang Mock ay nagbabalik ng default na halaga o nagtatapon ng exception, depende sa configuration.
Mock ay kinakailangan kapag ang code na sinusuri ay nakikipag-ugnayan sa mga component na may side effects: pagpapadala ng data sa server, pagsusulat sa database, pag-log, analytics, nabigasyon, pagpapakita ng system dialogs. Kung walang Mock, ang mga interaksyong ito ay hindi masusuri nang hindi pinapatakbo ang tunay na imprastraktura. Ayon sa Google Testing Blog, ang Mock ay ang tanging paraan upang suriin na ang application ay talagang nagpadala ng analytics event nang hindi nagtaas ng test server.
Ang pagkakaiba sa pagitan ng Mock at Stub ay isa sa mga pinaka-pinag-uusapang paksa sa pagsubok. Ang parehong uri ay pumapalit sa tunay na dependency, ngunit sa panimula na magkakaibang paraan.
| Kraytirya | Mock | Stub |
|---|---|---|
| Pangunahing tanong | Tinawag ba ang method? | Anong resulta ang ibinalik? |
| Beripikasyon | Pag-uugali (verify) | Estado (assert) |
| Pagbabalik ng datos | Opsyonal | Sapilitan |
| Halimbawa | verify(analytics).logEvent("click") | assertEquals(5, repository.getCount()) |
| Kailan gagamitin | Side effects | Pagbabalik ng datos |
Isang simpleng test para pumili: tanungin ang iyong sarili — “kung tatanggalin ko ang linyang ito ng code, babagsak ba ang test?”. Kung ang test ay sumusuri sa ibinalik na halaga — kailangan ang Stub (pagsusuri sa pamamagitan ng assert). Kung ang test ay sumusuri kung ang code ay tumawag ng method na may tamang mga argumento — kailangan ang Mock (pagsusuri sa pamamagitan ng verify). Ang dichotomiyang ito ay sumusunod mula sa pattern na Command-Query Separation: ang mga method na nagbabago ng estado (commands) ay nangangailangan ng Mock; ang mga method na nagbabalik ng datos (queries) ay nangangailangan ng Stub.
Ang pagpili sa pagitan ng Mockito at MockK ay isa sa mga unang desisyon sa pag-setup ng test stack ng isang Android project sa Kotlin. Ang parehong library ay gumaganap ng parehong gawain, ngunit may iba't ibang approach sa Kotlin-specific na mga tampok.
Mockito — ay ang de facto standard para sa mga Java project. Ang bersyon 5.x ay sumusuporta sa mock-object para sa final classes, static method at constructor salamat sa built-in na MockMaker. Para sa Kotlin project, ang Mockito ay nangangailangan ng karagdagang configuration: mockito-kotlin extension para sa pinahusay na syntax, mockito-inline para sa final classes. Hindi sinusuportahan ng Mockito ang Kotlin coroutine at suspend functions nang walang karagdagang adapters.
MockK ay ginawa partikular para sa Kotlin. Ito ay native na sumusuporta sa coroutine (coEvery, coVerify), sealed class, data class, object-singleton at extension functions. Ang syntax ng MockK ay gumagamit ng DSL na may lambda blocks, na natural na hitsura sa Kotlin code. Ang MockK ay maaari ring mag-mock ng mga property (property mocking) nang walang karagdagang configuration — ito ay mahalaga para sa Android project na gumagamit ng LiveData, StateFlow at 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
Ang mga benchmark (JVM Benchmark, 2024) ay nagpapakita na ang MockK ay lumilikha ng mock-object nang 15–20% mas mabilis kaysa Mockito para sa Kotlin project dahil sa direktang pagtatrabaho sa Kotlin bytecode, hindi Java Reflections. Para sa mga project na may libu-libong unit-test, ang pagkakaiba sa oras ng pagbuo ay maaaring kapansin-pansin: ang MockK ay nakakatipid ng 30–60 segundo sa buong pagtakbo ng test sa malalaking project.
Tatalakayin natin ang tatlong senaryo: pagsubok ng ViewModel na may Mock-dependencies, pagsubok ng UseCase na may beripikasyon ng API call at pagsubok ng coroutine na may 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])
}
}
Ang epektibong paggamit ng Mock sa mobile development ay nangangailangan ng disiplina. Ang paglabag sa mga patakarang ito ay ginagawang marupok na hadlang ang mga test na nasisira sa bawat refactoring.
Mahigpit na patakaran: Ang Mock ay ginagawa lamang para sa mga dependency na tumatawid sa hangganan ng application: API clients, database, file system, system services (LocationManager, BluetoothAdapter, Camera). Ang mga panloob na klase ng application — domain entities, Value Object, simpleng utilities — ay hindi dapat palitan ng Mock. Ang kanilang pag-uugali ay sinusuri sa pamamagitan ng tunay na bagay.
Ang bawat test ay dapat maglaman ng eksaktong isang lohikal na pagsusuri — alinman sa verify (para sa Mock), o assert (para sa Stub). Huwag paghaluin ang pagsusuri ng estado at pag-uugali sa isang test. Kung kailangan suriin pareho ang API call at ang resulta — gumawa ng dalawang magkahiwalay na test na may magkaibang pangalan. Ang patakarang ito, na kilala bilang “isang assert bawat test”, ay nagmula sa mga rekomendasyon ni Kent Beck (2002).
Bukod sa pangunahing mocking, may mga advanced na pamamaraan na lumulutas ng mga tiyak na gawain sa mobile development: pagsubok ng multi-threading, pagsusuri ng estado ng Flow at partial mocking ng mga tunay na bagay.
Spy (o partial mock) ay nagbibigay-daan sa paglikha ng bagay na nagtatalaga ng mga tawag sa tunay na implementasyon, ngunit nagpapahintulot sa pag-override ng mga indibidwal na method. Sa MockK, ang spyk ay nilikha batay sa tunay na instance ng klase: val repo = spyk(InMemoryUserRepository()). Ang mga tawag kung saan itinakda ang mga inaasahan sa pamamagitan ng every ay dumadaan sa Mock; ang iba — sa pamamagitan ng tunay na bagay. Ang Spy ay lalong kapaki-pakinabang para sa pagsubok ng legacy-code kung saan ang dependency injection ay hindi pa naipatupad, at kailangan lamang i-override ang isang method.
Sa mga modernong Android project sa Jetpack Compose, ang ViewModel ay naglalantad ng estado sa pamamagitan ng StateFlow. Ang MockK ay nagbibigay-daan sa mocking ng Flow dependencies, at ang Turbine library ay nagpapasimple ng beripikasyon ng emisyon. Ang klasikong pattern: MockK para sa UseCase na nagbabalik ng Flow, Turbine para sa beripikasyon ng ViewModel emission. Ang stack na ito ay inirerekomenda ng dokumentasyon ng Android Testing (Google, 2024) para sa mga project sa 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()
}
}
}
Mga Madalas Itanong
Mock — ay isang konsepto, uri ng Test Double na sumusuri ng pag-uugali. Mockito — ay isang library para sa paggawa ng Mock-object sa Java at Android. Iba pang library: MockK (Kotlin), EasyMock (Java), Cuckoo (iOS).
Para sa pagsubok ng suspend functions gamit ang Mock, gamitin ang MockK (coEvery / coVerify) o Mockito na may mockito-kotlin. Ang MockK ay sumusuporta sa coroutine nang native: ang coEvery ay tumutukoy sa pag-uugali ng suspend function, ang coVerify ay sumusuri sa tawag nito sa loob ng coroutine. Lahat ng suspend tawag ay dapat isagawa sa loob ng runTest (kotlinx-coroutines-test).
Oo. Sa MockK para dito ginagamit ang returnsMany: every { api.getData() } returnsMany listOf(response1, response2). Sa Mockito — ang chain na thenReturn(value1).thenReturn(value2). Ito ay kapaki-pakinabang para sa pagsubok ng pag-uugali sa sunud-sunod na tawag na may iba't ibang tugon.
Sa MockK gamitin ang annotation na @MockK na may field na relaxed = true at tawagin ang clearMocks(mock) sa @After method. Sa Mockito — Mockito.reset(mock). Pinakamahusay na kasanayan: gumawa ng bagong Mock para sa bawat test sa pamamagitan ng @Before upang alisin ang impluwensya sa pagitan ng mga test.
MockK ay wastong gumagana sa sealed class: every { useCase() } returns Result.Success(data). Mockito ay hindi direktang sumusuporta sa sealed class, nangangailangan ng mga paraan para makaiwas. Ito ay isa sa mga dahilan kung bakit para sa Kotlin project ay inirerekomenda ang MockK sa halip na Mockito.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din