Mock, gerçek bir bileşenin davranışını taklit eden ve onunla etkileşimleri doğrulamaya olanak tanıyan bir yedek nesnedir. Yalnızca önceden belirlenmiş bir değer döndüren Stub'ın aksine, Mock, yöntem çağrısının gerçekleştiğini, iletilen argümanları ve çağrı sayısını kaydeder. Mockito (2024)'ye göre, Mock, Java ve Kotlin projelerinde en popüler Test Double türüdür ve mobil uygulamaların birim testlerinin %70'inden fazlasında kullanılır.
Ana Noktalar
Mock, bir mocking framework'ü (Mockito, MockK, EasyMock) tarafından oluşturulan, bir arayüz veya sınıfı simüle eden ve yöntemlerine yapılan tüm çağrıları kaydeden bir nesnedir. Geliştirici beklentileri belirler: X yöntemi Y argümanlarıyla çağrılacak ve Z döndürecek. Test yürütüldükten sonra Mock, beklentilerin gerçek çağrılarla eşleşip eşleşmediğini doğrular.
Terim, Test Double'ların tiyatro metaforundan gelir: Mock, (Dummy gibi) yalnızca sahnede durmayan, bir rol oynayan ve onunla etkileşimin doğru olup olmadığını doğrulayan bir “taklitçidir”. Test edilen kod, Mock'un beklediği yöntemi çağırmazsa veya yanlış argümanlarla çağırırsa — test, ihlal edilen beklenti mesajıyla başarısız olur.
Mock, framework fabrikası aracılığıyla oluşturulur: mockk<MyInterface>() veya Mockito.mock(MyClass.java). Framework, tüm yöntem çağrılarını yakalayan bir proxy nesnesi oluşturur. Her çağrı, önceden tanımlanmış beklentilerle karşılaştırılır. Çağrı bir beklentiyle eşleşirse — belirtilen değer döndürülür. Eşleşmezse — Mock, yapılandırmaya bağlı olarak varsayılan bir değer döndürür veya bir istisna fırlatır.
Mock, test edilen kodun yan etkileri olan bileşenlerle (sunucuya veri gönderme, veritabanına yazma, günlükleme, analiz, gezinme, sistem diyaloglarını gösterme) etkileşime girmesi durumunda zorunludur. Mock olmadan, bu etkileşimler gerçek altyapı çalıştırılmadan doğrulanamaz. Google Testing Blog'a göre Mock, bir test sunucusu başlatmadan bir uygulamanın gerçekten bir analiz olayı gönderip göndermediğini doğrulamanın tek yoludur.
Mock ve Stub arasındaki fark, testlerde en çok tartışılan konulardan biridir. Her iki tür de gerçek bir bağımlılığı değiştirir, ancak temelde farklı şekillerde.
| Kriter | Mock | Stub |
|---|---|---|
| Temel soru | Yöntem çağrıldı mı? | Hangi sonuç döndürüldü? |
| Doğrulama | Davranış (verify) | Durum (assert) |
| Veri dönüşü | İsteğe bağlı | Zorunlu |
| Örnek | verify(analytics).logEvent(“click”) | assertEquals(5, repository.getCount()) |
| Ne zaman kullanılır | Yan etkiler | Veri dönüşü |
Karar vermek için basit bir test: kendinize sorun “bu kod satırını silersem test başarısız olur mu?” Test bir dönüş değerini kontrol ediyorsa — Stub'a (assert tabanlı doğrulama) ihtiyacınız var. Test, kodun doğru argümanlarla bir yöntemi çağırıp çağırmadığını kontrol ediyorsa — Mock'a (verify tabanlı doğrulama) ihtiyacınız var. Bu ikilik, Command-Query Separation modelini izler: durumu değiştiren yöntemler (commands) Mock'a ihtiyaç duyar; veri döndüren yöntemler (queries) Stub'a ihtiyaç duyar.
Mockito ve MockK arasında seçim yapmak, Kotlin'de bir Android projesinin test yığınını kurarken ilk kararlardan biridir. Her iki kütüphane de aynı amaca hizmet eder, ancak Kotlin'e özgü özelliklere farklı yaklaşımlarla.
Mockito, Java projeleri için fiili standarttır. Sürüm 5.x, yerleşik MockMaker sayesinde final sınıflar, statik yöntemler ve yapıcılar için mocking'i destekler. Kotlin projeleri için Mockito ek kurulum gerektirir: gelişmiş sözdizimi için mockito-kotlin uzantıları, final sınıflar için mockito-inline. Mockito, ek adaptörler olmadan Kotlin coroutine'lerini ve askıya alma işlevlerini desteklemez.
MockK özellikle Kotlin için oluşturulmuştur. Coroutine'leri (coEvery, coVerify), sealed class'ları, data class'ları, nesne singleton'larını ve extension işlevlerini yerel olarak destekler. MockK sözdizimi, lambda bloklarıyla DSL kullanır ve Kotlin kodunda doğal görünür. MockK ayrıca ek kurulum olmadan özellik mocking'ini de destekler — bu, LiveData, StateFlow ve Delegates kullanan Android projeleri için önemlidir.
// Mockito + mockito-kotlin
val repository = mock<UserRepository>()
whenever(repository.getUser(1)).thenReturn(user)
// MockK
val repository = mockk<UserRepository>()
every { repository.getUser(1) } returns user
Karşılaştırmalar (JVM Benchmark, 2024), MockK'nın Java Reflections yerine doğrudan Kotlin bayt koduyla çalışması sayesinde Kotlin projeleri için Mockito'dan %15–20 daha hızlı mock nesneleri oluşturduğunu göstermektedir. Binlerce birim testi olan projelerde, derleme hızındaki fark fark edilebilir: MockK, büyük projelerde tam test çalıştırmasında 30–60 saniye kazandırır.
Üç senaryoyu inceleyelim: Mock bağımlılıklarıyla ViewModel testi, API çağrısı doğrulamalı UseCase testi ve coVerify ile coroutine testi.
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])
}
}
Mobil geliştirmede Mock'un etkili kullanımı disiplin gerektirir. Bu kuralları ihlal etmek testleri, her yeniden düzenlemede kırılan kırılgan engellere dönüştürür.
Sert bir kural: Mock'lar yalnızca uygulama sınırını aşan bağımlılıklar için oluşturulmalıdır: API istemcileri, veritabanları, dosya sistemi, sistem hizmetleri (LocationManager, BluetoothAdapter, Camera). Dahili uygulama sınıfları — alan varlıkları, Value Objects, basit yardımcı programlar — Mock ile değiştirilmemelidir. Davranışları gerçek nesneler aracılığıyla test edilir.
Her test tam olarak bir mantıksal kontrol içermelidir — verify (Mock için) veya assert (Stub için). Aynı testte durum ve davranış doğrulamasını karıştırmayın. Hem bir API çağrısını hem de sonucunu kontrol etmeniz gerekiyorsa — farklı adlarla iki ayrı test oluşturun. “Test başına bir assert” olarak bilinen bu kural, Kent Beck'in (2002) önerilerine dayanır.
Temel mocking'in ötesinde, mobil geliştirmede belirli görevleri çözen gelişmiş teknikler vardır: çoklu iş parçacığı testi, Flow durumu doğrulama ve gerçek nesnelerin kısmi mocking'i.
Spy (veya kısmi mock), çağrıları gerçek uygulamaya devreden ancak tek tek yöntemleri geçersiz kılmaya izin veren bir nesne oluşturmayı sağlar. MockK'da, spyk gerçek bir sınıf örneğine dayanarak oluşturulur: val repo = spyk(InMemoryUserRepository()). every ile tanımlanan beklentilere sahip çağrılar Mock üzerinden gider; geri kalanı gerçek nesne üzerinden gider. Spy, bağımlılık enjeksiyonunun henüz uygulanmadığı ve yalnızca bir yöntemi geçersiz kılmanız gereken eski kodları test etmek için özellikle yararlıdır.
Jetpack Compose kullanan modern Android projelerinde ViewModel, durumu StateFlow aracılığıyla gösterir. MockK, Flow bağımlılıklarını mock'lamaya izin verir ve Turbine kütüphanesi, emisyon doğrulamasını basitleştirir. Klasik model: Flow döndüren bir UseCase için MockK, ViewModel emisyonlarını doğrulamak için Turbine. Bu yığın, Kotlin Coroutines kullanan projeler için Android Testing belgeleri (Google, 2024) tarafından önerilir.
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()
}
}
}
Sıkça sorulan sorular
Mock bir kavramdır, davranışı doğrulayan bir Test Double türüdür. Mockito, Java ve Android'de Mock nesneleri oluşturmak için bir kütüphanedir. Diğer kütüphaneler: MockK (Kotlin), EasyMock (Java), Cuckoo (iOS).
Mock ile askıya alma işlevlerini test etmek için MockK (coEvery / coVerify) veya mockito-kotlin ile Mockito kullanın. MockK, coroutine'leri yerel olarak destekler: coEvery, bir askıya alma işlevinin davranışını tanımlar, coVerify, bir coroutine içinde çağrılmasını doğrular. Tüm askıya alma çağrıları runTest (kotlinx-coroutines-test) içinde yürütülmelidir.
Evet. MockK'da returnsMany kullanın: every { api.getData() } returnsMany listOf(response1, response2). Mockito'da — bir thenReturn(value1).thenReturn(value2) zinciri. Bu, farklı yanıtlar döndüren sıralı çağrılarla davranışı test etmek için kullanışlıdır.
MockK'da relaxed = true ile @MockK ek açıklamasını kullanın ve @After yönteminde clearMocks(mock) çağrısı yapın. Mockito'da — Mockito.reset(mock). En iyi uygulama: testler arası müdahaleyi ortadan kaldırmak için @Before ile her test için yeni bir Mock oluşturun.
MockK, sealed class'larla doğru şekilde çalışır: every { useCase() } returns Result.Success(data). Mockito, sealed class'ları doğrudan desteklemez ve geçici çözümler gerektirir. Bu, Kotlin projelerinde Mockito yerine MockK'nın önerilmesinin nedenlerinden biridir.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun