Mock — adalah objek pengganti yang meniru perilaku komponen nyata dan memungkinkan pemeriksaan interaksi dengannya. Tidak seperti Stub yang hanya mengembalikan nilai yang ditentukan, Mock mencatat fakta pemanggilan metode, argumen yang diberikan, dan jumlah pemanggilan. Menurut data Mockito (2024), Mock adalah jenis Test Double paling populer di proyek Java dan Kotlin, digunakan di lebih dari 70% pengujian unit aplikasi seluler.
Poin Utama
Mock — adalah objek yang dibuat oleh framework mocking (Mockito, MockK, EasyMock), yang meniru antarmuka atau kelas dan mencatat semua panggilan metodenya. Pengembang menetapkan ekspektasi: metode X akan dipanggil dengan argumen Y dan mengembalikan Z. Setelah pengujian dijalankan, Mock memeriksa apakah ekspektasi sesuai dengan panggilan sebenarnya.
Istilah ini berasal dari metafora teater Test Doubles: Mock adalah “peniru” yang tidak hanya berdiri di atas panggung (seperti Dummy), tetapi memainkan peran dan memverifikasi apakah interaksi dengannya sudah benar. Jika kode yang diuji tidak memanggil metode yang diharapkan Mock, atau memanggilnya dengan argumen yang salah — pengujian gagal dengan pesan tentang ekspektasi yang dilanggar.
Mock dibuat melalui pabrik framework: mockk<MyInterface>() atau Mockito.mock(MyClass.java). Framework menghasilkan objek proxy yang mencegat semua panggilan metode. Setiap panggilan dibandingkan dengan ekspektasi (expectations) yang telah ditentukan sebelumnya. Jika panggilan sesuai dengan ekspektasi — nilai yang ditentukan dikembalikan. Jika tidak — Mock mengembalikan nilai default atau melempar pengecualian, tergantung pada konfigurasi.
Mock wajib digunakan ketika kode yang diuji berinteraksi dengan komponen yang memiliki efek samping: mengirim data ke server, menulis ke basis data, pencatatan log, analytics, navigasi, menampilkan dialog sistem. Tanpa Mock, interaksi ini tidak dapat diverifikasi tanpa menjalankan infrastruktur nyata. Menurut Google Testing Blog, Mock adalah satu-satunya cara untuk memeriksa bahwa aplikasi benar-benar mengirimkan peristiwa analytics tanpa menjalankan server pengujian.
Perbedaan antara Mock dan Stub adalah salah satu topik yang paling banyak diperdebatkan dalam pengujian. Kedua jenis menggantikan dependensi nyata, tetapi dengan cara yang mendasar berbeda.
| Kriteria | Mock | Stub |
|---|---|---|
| Pertanyaan utama | Apakah metode dipanggil? | Hasil apa yang dikembalikan? |
| Verifikasi | Perilaku (verify) | Keadaan (assert) |
| Pengembalian data | Opsional | Wajib |
| Contoh | verify(analytics).logEvent("click") | assertEquals(5, repository.getCount()) |
| Kapan digunakan | Efek samping | Pengembalian data |
Uji sederhana untuk memilih: tanyakan pada diri sendiri — “jika saya menghapus baris kode ini, apakah pengujian akan gagal?”. Jika pengujian memeriksa nilai yang dikembalikan — diperlukan Stub (pemeriksaan melalui assert). Jika pengujian memeriksa apakah kode memanggil metode dengan argumen yang benar — diperlukan Mock (pemeriksaan melalui verify). Dikotomi ini mengikuti pola Command-Query Separation: metode yang mengubah keadaan (commands) memerlukan Mock; metode yang mengembalikan data (queries) memerlukan Stub.
Pilihan antara Mockito dan MockK adalah salah satu keputusan pertama saat menyiapkan tumpukan pengujian proyek Android di Kotlin. Kedua pustaka melakukan tugas yang sama, tetapi dengan pendekatan berbeda terhadap spesifikasi Kotlin.
Mockito — adalah standar de facto untuk proyek Java. Versi 5.x mendukung objek mock untuk kelas final, metode statis, dan konstruktor berkat MockMaker bawaan. Untuk proyek Kotlin, Mockito memerlukan konfigurasi tambahan: ekstensi mockito-kotlin untuk sintaksis yang lebih baik, mockito-inline untuk kelas final. Mockito tidak mendukung coroutine Kotlin dan fungsi suspend tanpa adaptor tambahan.
MockK dibuat khusus untuk Kotlin. Ia mendukung secara native coroutine (coEvery, coVerify), sealed class, data class, singleton object, dan fungsi ekstensi. Sintaksis MockK menggunakan DSL dengan blok lambda, yang terlihat alami dalam kode Kotlin. MockK juga dapat melakukan mock properti (property mocking) tanpa konfigurasi tambahan — ini penting untuk proyek Android yang menggunakan LiveData, StateFlow, dan 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
Tolok ukur (JVM Benchmark, 2024) menunjukkan bahwa MockK membuat objek mock 15–20% lebih cepat daripada Mockito untuk proyek Kotlin berkat kerja langsung dengan bytecode Kotlin, bukan Java Reflections. Untuk proyek dengan ribuan pengujian unit, perbedaan waktu pembangunan bisa terlihat: MockK menghemat 30–60 detik pada proses pengujian penuh di proyek besar.
Kita akan memeriksa tiga skenario: menguji ViewModel dengan dependensi Mock, menguji UseCase dengan verifikasi panggilan API, dan menguji coroutine dengan 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])
}
}
Penggunaan Mock yang efektif dalam pengembangan seluler memerlukan disiplin. Pelanggaran aturan ini mengubah pengujian menjadi hambatan rapuh yang rusak pada setiap refactoring.
Aturan ketat: Mock dibuat hanya untuk dependensi yang melintasi batas aplikasi: klien API, basis data, sistem file, layanan sistem (LocationManager, BluetoothAdapter, Camera). Kelas internal aplikasi — entitas domain, Value Object, utilitas sederhana — tidak boleh diganti dengan Mock. Perilakunya diuji melalui objek nyata.
Setiap pengujian harus berisi tepat satu pemeriksaan logis — baik verify (untuk Mock), atau assert (untuk Stub). Jangan mencampur verifikasi keadaan dan perilaku dalam satu pengujian. Jika perlu memeriksa baik panggilan API maupun hasilnya — buat dua pengujian terpisah dengan nama berbeda. Aturan ini, dikenal sebagai “satu assert per pengujian”, berasal dari rekomendasi Kent Beck (2002).
Selain mocking dasar, ada teknik lanjutan yang menyelesaikan tugas spesifik dalam pengembangan seluler: menguji multi-threading, memeriksa keadaan Flow, dan mocking parsial objek nyata.
Spy (atau partial mock) memungkinkan pembuatan objek yang mendelegasikan panggilan ke implementasi nyata, tetapi memungkinkan penimpaan metode tertentu. Di MockK, spyk dibuat berdasarkan instance nyata kelas: val repo = spyk(InMemoryUserRepository()). Panggilan yang ekspektasinya ditetapkan melalui every melewati Mock; sisanya — melalui objek nyata. Spy sangat berguna untuk menguji kode legacy di mana injeksi dependensi belum diterapkan, dan hanya perlu menimpa satu metode.
Dalam proyek Android modern dengan Jetpack Compose, ViewModel mengekspos keadaan melalui StateFlow. MockK memungkinkan mocking dependensi Flow, dan pustaka Turbine menyederhanakan verifikasi emisi. Pola klasik: MockK untuk UseCase yang mengembalikan Flow, Turbine untuk memverifikasi emisi ViewModel. Tumpukan ini direkomendasikan oleh dokumentasi Android Testing (Google, 2024) untuk proyek pada 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()
}
}
}
Pertanyaan yang Sering Diajukan
Mock — adalah konsep, jenis Test Double yang memeriksa perilaku. Mockito — adalah pustaka untuk membuat objek Mock di Java dan Android. Pustaka lainnya: MockK (Kotlin), EasyMock (Java), Cuckoo (iOS).
Untuk menguji fungsi suspend dengan Mock, gunakan MockK (coEvery / coVerify) atau Mockito dengan mockito-kotlin. MockK mendukung coroutine secara native: coEvery mendefinisikan perilaku fungsi suspend, coVerify memeriksa panggilannya di dalam coroutine. Semua panggilan suspend harus dijalankan di dalam runTest (kotlinx-coroutines-test).
Ya. Di MockK untuk ini digunakan returnsMany: every { api.getData() } returnsMany listOf(response1, response2). Di Mockito — rantai thenReturn(value1).thenReturn(value2). Ini berguna untuk menguji perilaku pada panggilan berurutan dengan respons berbeda.
Di MockK gunakan anotasi @MockK dengan bidang relaxed = true dan panggil clearMocks(mock) di metode @After. Di Mockito — Mockito.reset(mock). Praktik terbaik: buat Mock baru untuk setiap pengujian melalui @Before untuk menghilangkan pengaruh antar pengujian.
MockK bekerja dengan benar dengan sealed class: every { useCase() } returns Result.Success(data). Mockito tidak mendukung sealed class secara langsung, memerlukan solusi alternatif. Ini adalah salah satu alasan mengapa untuk proyek Kotlin, MockK direkomendasikan daripada Mockito.
Ringkasan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga