Mockito: Mock ve Stub
Birim testlerin temel prensibi izolasyondur: test ettiğiniz sınıf, dış bağımlılıklarından (veritabanı, HTTP istemci, dosya sistemi) ayrılmalıdır. Mockito, Java ekosisteminin en popüler mocking framework'üdür ve Spring Boot ile sorunsuz çalışır. Bu derste Mockito'nun tüm temel ve ileri özelliklerini öğreneceğiz.
Mock, Stub, Spy — Temel Kavramlar
Bu üç kavram sıkça karıştırılır. Net olarak ayıralım:
Mock: Gerçek nesnenin "sahte" kopyasıdır. Tüm metotları tanımlıdır ama varsayılan olarak hiçbir şey yapmaz (null/0/false döner). Çağrıları kaydeder — sonra doğrulayabilirsiniz.
Stub: Belirli bir girdi için önceden tanımlanmış bir çıktı döndüren yapıdır.
when(...).thenReturn(...)ile oluşturulur.Spy: Gerçek nesneyi saran bir wrapper'dır. Varsayılan olarak gerçek metotları çağırır, sadece belirli metotları override edebilirsiniz.
Mockito Kurulumu
Spring Boot projelerinde Mockito spring-boot-starter-test ile gelir:
<!-- Otomatik olarak dahildir -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>@Mock ve @InjectMocks
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class) // Mockito anotasyonlarını aktifleştir
class OrderServiceTest {
@Mock
private ProductRepository productRepository; // Sahte repository
@Mock
private NotificationService notificationService; // Sahte notification
@InjectMocks
private OrderService orderService; // Mock'lar otomatik enjekte edilir
@Test
void shouldPlaceOrder() {
// @InjectMocks, OrderService'in constructor'ına
// @Mock ile işaretlenen nesneleri otomatik geçirir
}
}@InjectMocks, hedef sınıfın constructor'ına, setter'larına veya field'larına uygun mock'ları otomatik enjekte eder. Constructor injection tercih edilir — bu aynı zamanda Spring'in de önerdiği dependency injection yöntemidir.
when().thenReturn() — Davranış Tanımlama (Stubbing)
@Test
void shouldReturnProductWhenFound() {
// Arrange — Mock'un davranışını tanımla
Product laptop = new Product(1L, "Laptop", 999.99);
when(productRepository.findById(1L))
.thenReturn(Optional.of(laptop));
// Act
Product result = orderService.getProduct(1L);
// Assert
assertEquals("Laptop", result.getName());
assertEquals(999.99, result.getPrice());
}
@Test
void shouldReturnDefaultForUnknownProduct() {
// Stub tanımlanmamış çağrılar varsayılan değer döner
// Optional için: Optional.empty()
// Sayılar için: 0
// Boolean için: false
// Object için: null
when(productRepository.findById(anyLong()))
.thenReturn(Optional.empty());
assertThrows(ProductNotFoundException.class,
() -> orderService.getProduct(999L));
}Birden fazla çağrıda farklı sonuçlar döndürmek:
@Test
void shouldReturnDifferentResultsOnConsecutiveCalls() {
when(productRepository.count())
.thenReturn(0L) // İlk çağrı
.thenReturn(1L) // İkinci çağrı
.thenReturn(5L); // Üçüncü ve sonraki çağrılar
assertEquals(0L, productRepository.count());
assertEquals(1L, productRepository.count());
assertEquals(5L, productRepository.count());
assertEquals(5L, productRepository.count()); // Hep 5L döner
}verify() — Çağrı Doğrulama
Mock'un belirli metotlarının çağrılıp çağrılmadığını doğrulamak:
@Test
void shouldSendNotificationAfterOrder() {
Product product = new Product(1L, "Laptop", 999.99);
when(productRepository.findById(1L))
.thenReturn(Optional.of(product));
orderService.placeOrder(1L, 2);
// Notification gönderildi mi?
verify(notificationService).sendOrderConfirmation(any(Order.class));
// Tam olarak 1 kez çağrıldı mı?
verify(notificationService, times(1))
.sendOrderConfirmation(any());
// Hiç çağrılmadı mı?
verify(notificationService, never())
.sendOrderCancellation(any());
// En az / en fazla
verify(productRepository, atLeastOnce()).findById(anyLong());
verify(productRepository, atMost(3)).findById(anyLong());
}ArgumentCaptor — Argüman Yakalama
Bir mock'a geçirilen argümanları yakalayıp incelemek:
@Test
void shouldCaptureOrderDetails() {
Product product = new Product(1L, "Laptop", 999.99);
when(productRepository.findById(1L))
.thenReturn(Optional.of(product));
orderService.placeOrder(1L, 3);
// ArgumentCaptor ile geçirilen Order nesnesini yakala
ArgumentCaptor<Order> orderCaptor = ArgumentCaptor.forClass(Order.class);
verify(notificationService).sendOrderConfirmation(orderCaptor.capture());
// Yakalanan nesneyi incele
Order capturedOrder = orderCaptor.getValue();
assertEquals("Laptop", capturedOrder.getProductName());
assertEquals(3, capturedOrder.getQuantity());
assertEquals(2999.97, capturedOrder.getTotalPrice(), 0.01);
}doThrow() — Exception Fırlatma
void metotlar veya özel durumlar için:
@Test
void shouldHandleRepositoryFailure() {
// void metotlarda when() yerine doThrow() kullanılır
doThrow(new DataAccessException("DB error") {})
.when(productRepository).deleteById(1L);
assertThrows(ServiceException.class,
() -> orderService.deleteProduct(1L));
}
@Test
void shouldThrowForInvalidInput() {
when(productRepository.save(any()))
.thenThrow(new ConstraintViolationException("Duplicate", null, null));
assertThrows(DuplicateProductException.class,
() -> orderService.createProduct(duplicateProduct));
}Spy — Gerçek Nesne Üzerinde Kısmi Mock
@Test
void shouldUseSpy() {
// Gerçek listeyi spy ile sar
List<String> realList = new ArrayList<>();
List<String> spyList = spy(realList);
// Gerçek metotlar çalışır
spyList.add("one");
spyList.add("two");
assertEquals(2, spyList.size()); // Gerçek size()
// Belirli bir metodu override et
doReturn(100).when(spyList).size();
assertEquals(100, spyList.size()); // Artık 100 döner
// Ama eleman ekleme hâlâ gerçek
spyList.add("three");
verify(spyList).add("three"); // Çağrı kaydedildi
}⚠️ Spy kullanırken dikkat: when(spyList.size()) yerine doReturn(100).when(spyList).size() kullanın. İlk formatta gerçek metot önce çağrılır, bu yan etkilere yol açabilir.
Argument Matchers
@Test
void argumentMatcherExamples() {
when(productRepository.findByName(anyString()))
.thenReturn(Optional.of(new Product()));
when(productRepository.findByPriceRange(
eq(0.0), // tam eşitlik
anyDouble() // herhangi bir double
)).thenReturn(List.of());
// Özel matcher
when(productRepository.findByName(argThat(name ->
name.length() > 3 && name.startsWith("A")
))).thenReturn(Optional.of(new Product()));
}⚠️ Kural: Bir argümanda matcher kullanıyorsanız, tüm argümanlarda matcher kullanmalısınız. Karışık kullanım derleme hatasına neden olur: eq(value) ile sabit değeri matcher'a dönüştürün.
Mockito, Spring Boot test yazımının vazgeçilmez aracıdır. Mock ile izolasyon, stub ile davranış tanımlama, verify ile çağrı doğrulama ve ArgumentCaptor ile argüman inceleme — bu dört temel mekanizmayı ustaca kullanarak güvenilir birim testler yazabilirsiniz.
AI Asistan
Sorularını yanıtlamaya hazır