Java İle Unit Test Yazımında Mockito Kullanarak Servis Katmanı Testi Nasıl Yapılır?

Java İle Unit Test Yazımında Mockito Kullanarak Servis Katmanı Testi Nasıl Yapılır?
Java İle Unit Test Yazımında Mockito Kullanarak Servis Katmanı Testi Nasıl Yapılır?

Ön Hazırlık ve Gereksinimler

Test ortamınızı hazırlamak için projenize gerekli bağımlılıkları eklemeniz gerekir. 2026 yılı standartlarına uygun olarak, JUnit 5 (Jupiter) ve Mockito kütüphanelerini kullanacağız. Maven tabanlı bir projede pom.xml dosyanıza şu bağımlılıkları eklemelisiniz.


    org.junit.jupiter
    junit-jupiter-api
    5.10.0
    test


    org.mockito
    mockito-junit-jupiter
    5.5.0
    test

Bu bağımlılıklar, JUnit 5'in test yaşam döngüsü özelliklerini ve Mockito'nun anotasyon tabanlı (@Mock, @InjectMocks) kolaylıklarını projenize dahil eder.

Test Edilecek Servis Katmanını Tanımlama

Test yazmaya başlamadan önce, basit bir kullanıcı servis sınıfı oluşturalım. Bu servis, bir veritabanı katmanı olan UserRepository'ye bağımlıdır. Servis katmanının testini yaparken, UserRepository'nin gerçek bir veritabanına bağlanmasını değil, Mockito tarafından üretilen sahte bir nesne olmasını istiyoruz.

public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public String getUserName(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("Kullanıcı bulunamadı"));
    }
}

Yukarıdaki kodda, UserService sınıfı bağımlılık enjeksiyonu (dependency injection) prensibine göre tasarlanmıştır. Bu tasarım, test sırasında bağımlılığı kolayca değiştirmemize olanak tanır.

Mockito ile Mock Nesneleri Oluşturma

Mockito kullanarak test sınıfımızı yapılandıralım. @ExtendWith(MockitoExtension.class) anotasyonu, Mockito'nun test sınıfı içinde çalışmasını sağlar. @Mock ile sahte bir repository oluştururken, @InjectMocks ile bu sahte repository'yi servis sınıfımıza otomatik olarak enjekte ediyoruz.

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    void testGetUserName_Success() {
        // Test adımları burada olacak
    }
}

Bu yapı, testlerinizin temiz ve okunabilir olmasını sağlar. @InjectMocks, UserService'in kurucusuna @Mock ile işaretlenmiş UserRepository'yi otomatik olarak yerleştirir.

Davranış Tanımlama (Stubbing) ve Test Çalıştırma

Şimdi en kritik adıma geldik: Mock nesnesinin nasıl davranacağını belirtmek. when(...).thenReturn(...) yapısını kullanarak, veritabanı katmanının belirli bir girdi için ne döndüreceğini belirliyoruz. Bu, gerçek veritabanı sorgusunu atlamamızı sağlar.

@Test
void testGetUserName_Success() {
    // Hazırlık (Given)
    Long userId = 1L;
    when(userRepository.findById(userId)).thenReturn(Optional.of("Ahmet"));

    // İşlem (When)
    String result = userService.getUserName(userId);

    // Doğrulama (Then)
    assertEquals("Ahmet", result);
    verify(userRepository, times(1)).findById(userId);
}

Burada verify metodu, findById metodunun gerçekten bir kez çağrılıp çağrılmadığını kontrol eder. Bu, testin doğruluğunu kanıtlayan en güçlü araçlardan biridir.

Hata Durumlarını Test Etme

Servis katmanınızın sadece başarılı senaryolarını değil, hata durumlarını da test etmelisiniz. Örneğin, kullanıcı bulunamadığında servisin bir hata fırlatıp fırlatmadığını assertThrows ile kontrol edebiliriz.

@Test
void testGetUserName_NotFound() {
    Long userId = 99L;
    when(userRepository.findById(userId)).thenReturn(Optional.empty());

    assertThrows(RuntimeException.class, () -> {
        userService.getUserName(userId);
    });
}

Bu test, kodunuzun beklenmedik durumlarda (örneğin veritabanı boş döndüğünde) doğru hata yönetimini yapıp yapmadığını garanti altına alır.

Mockito Test Yöntemleri Karşılaştırması

Yöntem Avantajı Dezavantajı
@Mock Anotasyonu Okunabilirlik ve kolay yönetim Sadece JUnit 5 ile entegre çalışır
Mockito.mock() Esneklik, her yerde kullanılabilir Daha fazla kod yazımı gerektirir
ArgumentCaptor Karmaşık parametreleri doğrular Test kodunu biraz karmaşıklaştırır

Güvenlik Uyarısı: Birim testlerinizde asla gerçek veritabanı bağlantı bilgilerini veya şifrelerini kod içine gömmeyin. Mockito kullanmanın temel amacı, dış sistemlerden izole olmaktır. Eğer testlerinizde hassas veri simülasyonu yapıyorsanız, bu verilerin anonimleştirildiğinden emin olun.

Sıkça Sorulan Sorular

Mockito ile neden gerçek veritabanı kullanmıyoruz?

Gerçek veritabanı kullanmak testlerin yavaşlamasına, veritabanı kurulumuna ihtiyaç duymasına ve testlerin birbirinden etkilenmesine (side-effect) neden olur. Mockito, izole ve hızlı testler sağlar.

@Mock ve @InjectMocks arasındaki fark nedir?

@Mock, bağımlı olduğunuz sınıfın sahte bir kopyasını oluşturur. @InjectMocks ise bu sahte kopyaları, test ettiğiniz ana sınıfın içine otomatik olarak enjekte eder.

Testlerimde neden NullPointerException alıyorum?

Genellikle @ExtendWith(MockitoExtension.class) anotasyonunu eklemeyi unuttuğunuzda veya @Mock nesneleri başlatılmadığında bu hatayı alırsınız. Mockito anotasyonlarının düzgün işlenmesi için bu anotasyon şarttır.

Özel bir metodu nasıl mock'layabilirim?

Mockito standart olarak özel (private) metotları mock'lamaz. Eğer özel bir metodu mock'lamanız gerekiyorsa, bu genellikle tasarımınızın (design) hatalı olduğunun bir işaretidir; metodu public veya protected yaparak bağımlılığı dışarıdan enjekte edilebilir hale getirmelisiniz.

verify() metodunu kullanmak zorunda mıyım?

Zorunlu değildir ancak birim testin kalitesini artırır. Bir metodun sadece doğru sonucu döndürmesi değil, aynı zamanda doğru bağımlılıkları doğru parametrelerle çağırdığından emin olmak için verify() kullanılması önerilir.

ArgumentCaptor ile Parametre Doğrulama

Bazı durumlarda, servis katmanınızın bağımlı olduğu bir metoda gönderilen parametrenin içeriğini detaylıca incelemeniz gerekebilir. verify() metodu basit eşleşmeler için yeterli olsa da, metodun içine gönderilen nesnenin içindeki alanları kontrol etmek için ArgumentCaptor sınıfı kullanılır.

Örneğin, bir kullanıcı oluşturma servisinde veritabanına kaydedilen User nesnesinin e-posta adresinin doğru formatta olup olmadığını kontrol etmek istediğinizi varsayalım:

@Test
void shouldCaptureUserArgument() {
    // Given
    User user = new User("Ahmet", "ahmet@example.com");
    ArgumentCaptor userCaptor = ArgumentCaptor.forClass(User.class);

    // When
    userService.registerUser(user);

    // Then
    verify(userRepository).save(userCaptor.capture());
    User capturedUser = userCaptor.getValue();
    assertEquals("ahmet@example.com", capturedUser.getEmail());
}

Mockito ile İleri Seviye Hata Senaryoları: doThrow Kullanımı

when(...).thenReturn(...) yapısı, metodun dönüş değerini kontrol etmek için harikadır. Ancak, void dönen veya istisna fırlatması beklenen metodlarda doThrow() kullanımı daha temiz bir yaklaşım sunar. Özellikle bir veritabanı bağlantı hatasını simüle etmek için bu yöntemi tercih etmelisiniz.

Aşağıdaki örnekte, veritabanı katmanında bir hata oluştuğunda servis katmanımızın bu hatayı nasıl yönettiğini test ediyoruz:

@Test
void shouldThrowExceptionWhenDatabaseIsDown() {
    // Given
    User user = new User("Mehmet", "mehmet@example.com");
    doThrow(new RuntimeException("Veritabanı bağlantı hatası"))
        .when(userRepository).save(any(User.class));

    // When & Then
    assertThrows(RuntimeException.class, () -> {
        userService.registerUser(user);
    });
}

Test Kodlarında Performans ve Temiz Kod İpuçları

Birim testleriniz arttıkça, test paketinizin çalışma süresi uzayabilir. Mockito kullanırken performans ve okunabilirlik için şu kurallara dikkat etmeniz önerilir:

  • Statik Mocking'den Kaçının: Mockito.mockStatic() kullanımı testlerinizi kırılgan hale getirebilir. Mümkünse bağımlılıkları enjeksiyon (Dependency Injection) yoluyla yönetin.
  • Testleri İzolasyon İçinde Tutun: Her test metodu, diğerinden bağımsız bir şekilde çalışmalıdır. @BeforeEach kullanarak her testten önce mock nesnelerini sıfırlayın.
  • Gereksiz Mocking Yapmayın: Sadece test ettiğiniz servis katmanının doğrudan bağımlılıklarını mock'layın. "Mocking" işlemini aşırıya kaçmak, testlerin kodun gerçek davranışını yansıtmasını engelleyebilir.
Yöntem Kullanım Amacı
ArgumentCaptor Metoda gönderilen karmaşık nesnelerin içeriğini doğrulamak için.
doThrow Void metodlarda veya istisna fırlatılmasını istediğiniz durumlarda.
spy() Gerçek bir nesnenin bazı metodlarını mock'layıp diğerlerini gerçek haliyle kullanmak için.

Sonuç

Java ile servis katmanı testi yazarken Mockito kullanmak, kodunuzun kalitesini ve sürdürülebilirliğini artıran en önemli disiplinlerden biridir. Bu rehberde öğrendiğiniz @Mock, @InjectMocks ve when-thenReturn yapıları, profesyonel seviyede test yazımı için temel taşları oluşturur. Bir sonraki adım olarak, ArgumentCaptor kullanarak karmaşık nesne doğrulamaları yapmayı ve Mockito'nun doThrow özelliklerini inceleyerek hata senaryolarınızı daha da detaylandırmayı deneyebilirsiniz.

Sorumluluk Reddi: Bu makaledeki kod örnekleri eğitim amaçlıdır. Üretim ortamında (production) test kodlarınızın güvenliği ve veri izolasyonu tamamen geliştiricinin sorumluluğundadır. Hassas verilerle çalışırken her zaman güvenlik standartlarına uygun hareket edin.

Bu yazıya tepkinizi paylaşın:
Zeynep Kaya

Adım adım rehber hazırlama ve kullanıcı deneyimi odaklı içerik mimarisi konusunda yetkinim. Okuyucuların sorunlarını hızlı çözen, net ve uygulanabilir metinler üretmeyi seviyorum.

Yorumlar (0)

Yorum Yaz