Takip et

Mockito Verify: Detaylı Bir Teknik Rehber

Mockito Verify: Detaylı Bir Teknik Rehber Yazılım geliştirme süreçlerinde, özellikle birim testleri (unit tests) yazarken, kodumuzun beklene

Mockito Verify: Detaylı Bir Teknik Rehber

Yazılım geliştirme süreçlerinde, özellikle birim testleri (unit tests) yazarken, kodumuzun beklenen şekilde çalıştığından emin olmak kritik öneme sahiptir. Mockito, Java uygulamaları için popüler bir mocking framework’üdür ve bu süreçte bize büyük kolaylıklar sağlar. Mockito’nun en güçlü özelliklerinden biri de verify() metodudur. Bu rehberde, Mockito.verify()‘ın ne olduğunu, nasıl kullanıldığını, çeşitli senaryolarda nasıl uygulandığını ve testlerinizi daha sağlam hale getirmek için pratik ipuçlarını ayrıntılı olarak inceleyeceğiz.

Mockito Verify’a Giriş ve Temel Kullanım

Birim testlerinde, test ettiğimiz bir sınıfın (System Under Test – SUT) bağımlılıklarını izole etmek isteriz. Bu bağımlılıklar genellikle diğer sınıflar veya servislerdir. Mockito, bu bağımlılıkların “sahte” (mock) versiyonlarını oluşturmamızı sağlar. Bu sahte nesneler (mock objects) sayesinde, SUT’un kendi mantığına odaklanabiliriz, bağımlılıkların gerçek davranışlarına veya dış etkilere takılmayız.

Neden Verify Kullanırız?

Mockito.verify() metodu, bir mock nesne üzerinde belirli bir metodun belirli argümanlarla ve belirli sayıda çağrılıp çağrılmadığını doğrulamak için kullanılır. Bu, SUT’un bağımlılıklarıyla doğru şekilde etkileşime girdiğinden emin olmanın tek yoludur. Örneğin, bir servis metodunun başarılı bir işlemden sonra bir veritabanı kaydını kaydetme metodunu çağırdığını veya bir hata durumunda bir loglama metodunu çağırdığını doğrulamak isteyebilirsiniz. verify() olmadan, SUT’un içsel davranışlarını değil, sadece nihai çıktısını test edersiniz ki bu da bazı kritik etkileşimlerin gözden kaçmasına neden olabilir.

Temel Verify Söz Dizimi

Temel verify() kullanımı oldukça basittir:

import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.mockito.Mockito.*;

// Test edilecek bir servis
interface UserService {
    void createUser(String username, String email);
    void deleteUser(String username);
    String getUserDetails(String username);
}

// UserService'i kullanan bir sınıf
class UserProcessor {
    private UserService userService;

    public UserProcessor(UserService userService) {
        this.userService = userService;
    }

    public void processNewUser(String username, String email) {
        // Bazı iş mantığı...
        userService.createUser(username, email);
    }

    public void removeExistingUser(String username) {
        // Bazı iş mantığı...
        userService.deleteUser(username);
    }
}

public class UserProcessorTest {

    @Test
    void shouldCreateUserWhenProcessingNewUser() {
        // 1. Mock nesnesi oluştur
        UserService mockUserService = mock(UserService.class);

        // 2. Test edilecek sınıfı (SUT) mock ile başlat
        UserProcessor userProcessor = new UserProcessor(mockUserService);

        // 3. SUT üzerinde bir metod çağır
        userProcessor.processNewUser("testuser", "test@example.com");

        // 4. Mock nesnesi üzerindeki etkileşimi doğrula
        // mockUserService'in createUser metodunun "testuser" ve "test@example.com" argümanlarıyla çağrıldığını doğrula
        verify(mockUserService).createUser("testuser", "test@example.com");
    }

    @Test
    void shouldDeleteUserWhenRemovingExistingUser() {
        UserService mockUserService = mock(UserService.class);
        UserProcessor userProcessor = new UserProcessor(mockUserService);

        userProcessor.removeExistingUser("olduser");

        // mockUserService'in deleteUser metodunun "olduser" argümanıyla çağrıldığını doğrula
        verify(mockUserService).deleteUser("olduser");
    }
}

Bu örnekte, UserProcessor sınıfının processNewUser metodunun gerçekten de UserService üzerindeki createUser metodunu belirli argümanlarla çağırdığını doğrulamış olduk. Eğer createUser metodu hiç çağrılmazsa veya farklı argümanlarla çağrılırsa, test başarısız olacaktır.

Çağrı Sayısını Doğrulama

Bazen bir metodun sadece çağrılıp çağrılmadığını değil, aynı zamanda kaç kez çağrıldığını da doğrulamamız gerekebilir. Mockito, bunun için çeşitli seçenekler sunar.

Tam Sayıda Çağrı Doğrulama (times())

Bir metodun tam olarak belirli bir sayıda çağrıldığını doğrulamak için times(int count) kullanırız.

// ... önceki sınıflar ve interface'ler aynı ...

public class UserProcessorCallCountTest {

    @Test
    void shouldCallCreateUserOnceForSingleUser() {
        UserService mockUserService = mock(UserService.class);
        UserProcessor userProcessor = new UserProcessor(mockUserService);

        userProcessor.processNewUser("user1", "user1@example.com");

        // createUser metodunun tam olarak 1 kez çağrıldığını doğrula
        verify(mockUserService, times(1)).createUser("user1", "user1@example.com");
    }

    @Test
    void shouldCallCreateUserMultipleTimesForBatchProcessing() {
        UserService mockUserService = mock(UserService.class);
        UserProcessor userProcessor = new UserProcessor(mockUserService);

        // Varsayalım ki UserProcessor'da böyle bir metod var
        // public void processMultipleUsers(List> users) { ... }
        // Basitlik için test içinde birden fazla çağrı yapalım
        userProcessor.processNewUser("userA", "a@example.com");
        userProcessor.processNewUser("userB", "b@example.com");
        userProcessor.processNewUser("userC", "c@example.com");

        // createUser metodunun tam olarak 3 kez çağrıldığını doğrula
        verify(mockUserService, times(3)).createUser(anyString(), anyString()); // Argüman eşleştiricilerle
    }
}

En Az Bir Kez Çağrı Doğrulama (atLeastOnce())

Bir metodun en az bir kez çağrıldığından emin olmak için atLeastOnce() kullanırız.

// ...
@Test
void shouldCallDeleteUserAtLeastOnceWhenRemovingUsers() {
    UserService mockUserService = mock(UserService.class);
    UserProcessor userProcessor = new UserProcessor(mockUserService);

    userProcessor.removeExistingUser("userX");
    // Belki bir koşul altında bir kez daha çağrılıyor olabilir,
    // ama test sadece en az bir kez çağrıldığından emin olmak istiyor.
    // userProcessor.removeExistingUser("userX"); // Bu satır olsa da test geçerdi

    verify(mockUserService, atLeastOnce()).deleteUser("userX");
}

Hiç Çağrılmadığını Doğrulama (never())

Bir metodun kesinlikle hiç çağrılmadığını doğrulamak için never() kullanırız. times(0) ile aynıdır.

// ...
@Test
void shouldNeverCallDeleteUserWhenOnlyCreatingUsers() {
    UserService mockUserService = mock(UserService.class);
    UserProcessor userProcessor = new UserProcessor(mockUserService);

    userProcessor.processNewUser("newuser", "new@example.com");
    // deleteUser metodunun hiç çağrılmadığını doğrula
    verify(mockUserService, never()).deleteUser(anyString());
}

En Az/En Fazla Belirli Sayıda Çağrı (atLeast(), atMost())

Bir metodun belirli bir sayıdan en az veya en fazla çağrıldığını doğrulamak için atLeast(int min) ve atMost(int max) kullanırız.

// ...
@Test
void shouldCallCreateUserAtLeastTwiceAndAtMostFiveTimes() {
    UserService mockUserService = mock(UserService.class);
    UserProcessor userProcessor = new UserProcessor(mockUserService);

    userProcessor.processNewUser("u1", "u1@example.com");
    userProcessor.processNewUser("u2", "u2@example.com");
    userProcessor.processNewUser("u3", "u3@example.com"); // 3 kez çağrıldı

    verify(mockUserService, atLeast(2)).createUser(anyString(), anyString()); // En az 2 kez çağrıldı mı? Evet.
    verify(mockUserService, atMost(5)).createUser(anyString(), anyString());  // En fazla 5 kez çağrıldı mı? Evet.
}

Argümanları Doğrulama

Bir metodun doğru sayıda çağrılmasının yanı sıra, doğru argümanlarla çağrıldığından emin olmak da çok önemlidir.

Tam Eşleşme ile Argüman Doğrulama

En basit argüman doğrulama şekli, beklenen argümanları doğrudan verify() metoduna geçirmektir. Bu, argümanların tam olarak eşleşmesini gerektirir.

// ...
@Test
void shouldCallCreateUserWithExactArguments() {
    UserService mockUserService = mock(UserService.class);
    UserProcessor userProcessor = new UserProcessor(mockUserService);

    userProcessor.processNewUser("exactUser", "exact@example.com");

    // createUser metodunun tam olarak bu argümanlarla çağrıldığını doğrula
    verify(mockUserService).createUser("exactUser", "exact@example.com");
}

Argüman Eşleştiriciler (Argument Matchers) ile Doğrulama

Bazen argümanların tam değerlerini bilmeyiz veya esnek bir eşleşme sağlamak isteriz. Mockito, bunun için ArgumentMatchers sınıfını sağlar.

* any(): Herhangi bir nesne.
* anyString(), anyInt(), anyLong() vb.: Belirli bir türden herhangi bir değer.
* eq(value): Belirli bir değere eşit. any() ile karıştığında kullanılır.
* isA(Class> clazz): Belirli bir türün örneği.
* argThat(Matcher matcher): Özel bir eşleşme mantığı tanımlamak için kullanılır.

import org.mockito.ArgumentMatchers; // Import etmeyi unutmayın

// ...
@Test
void shouldCallCreateUserWithAnyStringArguments() {
    UserService mockUserService = mock(UserService.class);
    UserProcessor userProcessor = new UserProcessor(mockUserService);

    userProcessor.processNewUser("dynamicUser123", "dynamic@mail.com");

    // createUser metodunun herhangi iki String argümanıyla çağrıldığını doğrula
    verify(mockUserService).createUser(anyString(), anyString());
}

@Test
void shouldCallDeleteUserWithSpecificUsernameAndAnyEmail() {
    UserService mockUserService = mock(UserService.class);
    UserProcessor userProcessor = new UserProcessor(mockUserService);

    // Varsayalım ki deleteUser metodu iki argüman alıyor
    // interface UserService { void deleteUser(String username, String reason); }
    // Test için UserProcessor'ı biraz değiştirelim
    // public void removeExistingUser(String username, String reason) { userService.deleteUser(username, reason); }

    // Bu test senaryosu için orijinal deleteUser(String) metodunu kullanacağız
    userProcessor.removeExistingUser("adminUser");

    // deleteUser metodunun "adminUser" ile ve herhangi bir String (eğer olsaydı) çağrıldığını doğrula
    verify(mockUserService).deleteUser(eq("adminUser")); // Tek argüman olduğu için eq() gerekli değil ama gösterim amaçlı
}

@Test
void shouldCallCreateUserWithArgumentMatcher() {
    UserService mockUserService = mock(UserService.class);
    UserProcessor userProcessor = new UserProcessor(mockUserService);

    userProcessor.processNewUser("longusername", "test@example.com");

    // Kullanıcı adının 8 karakterden uzun olduğunu doğrula
    verify(mockUserService).createUser(argThat(username -> username.length() > 8), anyString());
}

Pratik Bilgi: Argüman eşleştiriciler kullanırken dikkatli olmalısınız. Eğer bir verify() çağrısında birden fazla argüman varsa ve bunlardan bir tanesi için any() veya benzeri bir eşleştirici kullanıyorsanız, diğer tüm argümanlar için de eşleştirici kullanmanız GEREKİR. Aksi takdirde, InvalidUseOfMatchersException hatası alırsınız. Örneğin, verify(mock).method(anyString(), "fixedValue"); hatalıdır. Doğrusu verify(mock).method(anyString(), eq("fixedValue")); olmalıdır.

Gelişmiş Verify Kullanımları ve Pratik İpuçları

Mockito verify()‘ın temel ve çağrı sayısına göre kullanımlarını gördük. Şimdi daha karmaşık senaryolar ve iyi pratikler üzerine odaklanalım.

Çağrı Sırasını Doğrulama (InOrder)

Bazı durumlarda, bir mock üzerindeki metod çağrılarının sadece gerçekleşip gerçekleşmediği değil, aynı zamanda belirli bir sırada gerçekleşip gerçekleşmediği de önemlidir. InOrder sınıfı bu ihtiyacı karşılar.

import org.mockito.InOrder;

// ...
interface OrderService {
    void prepareOrder(String orderId);
    void processPayment(String orderId, double amount);
    void shipOrder(String orderId);
}

class OrderProcessor {
    private OrderService orderService;

    public OrderProcessor(OrderService orderService) {
        this.orderService = orderService;
    }

    public void completeOrderWorkflow(String orderId, double amount) {
        orderService.prepareOrder(orderId);
        orderService.processPayment(orderId, amount);
        orderService.shipOrder(orderId);
    }
}

public class OrderProcessorTest {

    @Test
    void shouldExecuteOrderStepsInCorrectSequence() {
        OrderService mockOrderService = mock(OrderService.class);
        OrderProcessor orderProcessor = new OrderProcessor(mockOrderService);

        orderProcessor.completeOrderWorkflow("ORD-001", 100.0);

        // Çağrı sırasını doğrulamak için InOrder nesnesi oluştur
        InOrder inOrder = inOrder(mockOrderService);

        // Metodların belirtilen sırada çağrıldığını doğrula
        inOrder.verify(mockOrderService).prepareOrder("ORD-001");
        inOrder.verify(mockOrderService).processPayment("ORD-001", 100.0);
        inOrder.verify(mockOrderService).shipOrder("ORD-001");
    }
}

InOrder ile birden fazla mock nesnesinin çağrı sırasını da doğrulayabilirsiniz: InOrder inOrder = inOrder(mock1, mock2);

Tüm Etkileşimleri Doğrulama (verifyNoMoreInteractions(), verifyZeroInteractions())

Bazen bir mock nesnesi üzerinde beklenen etkileşimler dışında hiçbir etkileşim olmamasını sağlamak isteyebilirsiniz.

* verifyNoMoreInteractions(Object... mocks): Belirtilen mock(lar) üzerinde, bu noktaya kadar doğrulanmamış (unverified) başka bir etkileşimin olmadığını doğrular. Genellikle testin sonunda kullanılır.
* verifyZeroInteractions(Object... mocks): Belirtilen mock(lar) üzerinde hiçbir etkileşimin olmadığını doğrular. verify(mock, never()).someMethod() ile tüm metodlar için tek tek never() çağırmaya benzer ama daha kısadır.

// ...
@Test
void shouldNotHaveAnyOtherInteractionsAfterExpectedOnes() {
    UserService mockUserService = mock(UserService.class);
    UserProcessor userProcessor = new UserProcessor(mockUserService);

    userProcessor.processNewUser("user1", "user1@example.com");

    verify(mockUserService).createUser("user1", "user1@example.com");

    // createUser dışında başka hiçbir metodun çağrılmadığını doğrula
    verifyNoMoreInteractions(mockUserService);
}

@Test
void shouldHaveZeroInteractionsForInactiveProcessor() {
    UserService mockUserService = mock(UserService.class);
    UserProcessor userProcessor = new UserProcessor(mockUserService);

    // UserProcessor üzerinde hiçbir metod çağırmadık

    // mockUserService üzerinde hiçbir etkileşimin olmadığını doğrula
    verifyZeroInteractions(mockUserService);
}

Pratik Bilgi: verifyNoMoreInteractions() dikkatli kullanılmalıdır. Bazı geliştiriciler bunu her testin sonunda kullanmayı severken, bazıları bunun testleri çok kırılgan hale getirdiğini düşünür. Genellikle, test ettiğiniz bir metodun sadece beklediğiniz bağımlılıkları çağırdığından emin olmak istediğinizde faydalıdır.

Doğrulama Sıfırlama (reset())

Mockito.reset(mockObject) metodu, bir mock nesnesinin tüm when() stubbing’lerini ve verify() çağrı geçmişini temizler. Genellikle testler arasında izolasyonu sağlamak için her testte yeni bir mock oluşturmak tercih edildiğinden, reset() çok sık kullanılmaz. Ancak, aynı mock nesnesini birden fazla test senaryosunda kullanmanız gerektiğinde (ki bu genellikle kötü bir test tasarımıdır), reset() faydalı olabilir.

Pratik Bilgi: reset() kullanmak yerine, her test metodunda @BeforeEach ile veya doğrudan test metodunun içinde yeni mock nesneleri oluşturmak daha temiz ve daha okunabilir testler yazmanızı sağlar.

İyi Test Pratikleri

* Tek Sorumluluk Prensibi (Single Responsibility Principle) Testlerde de Geçerlidir: Her test metodu sadece bir şeyi doğrulamalıdır. Çok sayıda verify() çağrısı içeren testler genellikle birden fazla senaryoyu veya davranışı test etmeye çalışıyordur.
* Davranışı Doğrula, İç Detayları Değil: verify()‘ı, test ettiğiniz birimin dış bağımlılıklarıyla nasıl etkileşime girdiğini doğrulamak için kullanın, içsel implementasyon detaylarını değil. Aşırı spesifik verify() çağrıları, kod refaktör edildiğinde testlerin gereksiz yere kırılmasına neden olabilir.
* Over-specification’dan Kaçın: Her verify() çağrısı, testin kırılma olasılığını artırır. Yalnızca kritik etkileşimleri doğrulayın.
* BDD Tarzı Testler İçin then(): Eğer BDD (Behavior-Driven Development) tarzı testler yazıyorsanız, Mockito’nun BDDMockito sınıfındaki then() metodunu kullanmayı düşünebilirsiniz. then(mock).should(times(1)).method(); söz dizimi, verify(mock, times(1)).method(); ile aynı işlevi görür ancak daha çok “davranış” vurgusu yapar.

Sonuç ve Sıkça Sorulan Sorular (SSS)

Mockito.verify() metodu, birim testlerinizi daha güvenilir ve anlamlı hale getiren vazgeçilmez bir araçtır. Bir sınıfın bağımlılıklarıyla doğru şekilde etkileşime girdiğinden emin olarak, kodunuzun kalitesini artırır ve beklenmedik hataları erken aşamada yakalamanıza yardımcı olur. Doğru argümanlarla, doğru sayıda ve hatta doğru sırada çağrıları doğrulayarak, yazılımınızın davranışsal doğruluğunu garanti altına alabilirsiniz.

Sıkça Sorulan Sorular (SSS)

S1: verify() ile when() arasındaki fark nedir?
C1: when() (veya doReturn().when()), bir mock nesnesi üzerindeki bir metodun çağrıldığında ne yapacağını (hangi değeri döndüreceğini veya hangi exception’ı fırlatacağını) belirtmek için kullanılır. Bu, “stubbing” olarak adlandırılır. verify() ise, bir mock nesnesi üzerindeki bir metodun gerçekten çağrılıp çağrılmadığını, kaç kez çağrıldığını ve hangi argümanlarla çağrıldığını kontrol etmek için kullanılır. when() bir davranış tanımlarken, verify() bir davranışı doğrular.

S2: verify() kullanmak ne zaman gereklidir?
C2: verify() genellikle SUT’un bir bağımlılığı üzerinde bir “yan etki” (side effect) yaratmasını beklediğinizde gereklidir. Örneğin, bir veritabanına kayıt yazma, bir e-posta gönderme, bir olay yayınlama veya bir dosya sistemiyle etkileşim kurma gibi işlemler. Bu tür işlemlerin sonucunu doğrudan SUT’un dönüş değeriyle gözlemleyemediğiniz durumlarda, verify() kullanarak bu etkileşimlerin gerçekleştiğinden emin olursunuz.

S3: verifyNoMoreInteractions() her zaman kullanılmalı mı?
C3: Hayır, her zaman kullanılmak zorunda değildir ve bazı durumlarda testleri gereksiz yere kırılgan hale getirebilir. verifyNoMoreInteractions()‘ı, test ettiğiniz metodun sadece beklediğiniz bağımlılıkları çağırdığından ve başka hiçbir “gereksiz” çağrı yapmadığından emin olmak istediğinizde kullanmalısınız. Aşırıya kaçmak, test kodunuzu refaktör etmeyi zorlaştırabilir.

S4: Neden Mockito.verify() başarısız oluyor? (Yaygın hatalar)
C4:
* Metod hiç çağrılmadı: En yaygın neden. SUT’taki iş mantığı, beklediğiniz metodu çağırmıyor.
* Farklı argümanlarla çağrıldı: Metod çağrıldı ama beklediğiniz argümanlarla değil. Argüman eşleştiricileri doğru kullandığınızdan emin olun.
* Yanlış çağrı sayısı: times(1) beklerken metod 0 veya 2 kez çağrıldı.
* Argüman eşleştirici kuralları ihlali: Bir verify() çağrısında hem sabit değerler hem de anyString() gibi eşleştiriciler karıştırıldığında oluşur (tüm argümanlar için eşleştirici kullanılmalı).
* Yanlış mock nesnesi: verify() çağrısında yanlış mock nesnesini kullanmak.

S5: ArgumentMatchers kullanırken nelere dikkat etmeliyim?
C5:
* Bir verify (veya when) çağrısında bir ArgumentMatcher kullanıyorsanız, o çağrıdaki TÜM argümanlar için bir eşleştirici kullanmanız gerekir.
* eq() eşleştiricisi, sabit bir değeri belirtmek için kullanılır ve diğer eşleştiricilerle karıştırıldığında çok kullanışlıdır: verify(mock).method(anyString(), eq("fixedValue"));
* Özel eşleşme mantığı için argThat() kullanın.
* any() yerine daha spesifik olan anyString(), anyInt() gibi eşleştiricileri tercih etmek, tip güvenliğini artırır ve daha net bir ifade sağlar.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Gönder

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.
Mobil sürümden çık