Moq’nun efektif kullanımı — Mocking Framework .Net

Orhun Begendi
8 min readJul 21, 2019

--

Mock & Isolation

Merhabalar herkese,

Test yazmak zor, zaman alan ve üretiminizi çok yavaşlatabilir. Hatta küçük bir unit’i test etmek için class isolation(izole) yapamayabilirsiniz.

Bu yazıda size .Net Core üzerinden daha efektif ve izole test yazabilmeniz için popüler bir Mocking Framework’ü olan Moq.Net ve kullanımı için işe yarar ipuçları vereceğim. İlk olarak basit ayarlar ve method’larına bakacağız. Daha sonra testing’de sağladığı faydalar ve son olarak da gerçek bir isolation sağlamak için bazı tekniklere değineceğim.

Serinin diğer yazıları için;

1- Unit test

2- Test için araç kutusu ve kullanım talimatları

3- Test yazarken bilmeniz gerekenler sıralı tam liste

4- Unit test türleri nelerdir ve hangileri işinize yarar?

5- Moq’nun efektif kullanımı (bu yazı)

6- Unit test için design pattern’lar (yakında)

7- Testable architecture’a doğru (yakında)

8- Testable architecture (yakında)

9- Temel TDD (yakında)

10- Eski projelerinize yeni bir soluk TDD (yakında)

11- Advanced Unit Testing Teknikleri (yakında)

12- AspNet Core için Unit Testability (yakında)

Mocking’e genel bakış

Mock, test konseptinde sahte (fake) object üretmektir. Faydası Unit Test’in temel kurallarından olan Test In Isolation prensibini karmaşık yapılara uygulayabilmemizdir. Fake object konseptini Mock Framework’leri öncesi test yazanların tanıdığı bir durum. Mocking aynı işi yapan fake object’lerle çözülürdü. Bir tane web service kullanımı yapan class’ınız varsa bu class’ın aynı method’ları içeren fake versiyonu kodlayıp test içerisinde bu object oluşturularak kullanılır. Bu yöntemi farklı bloglarda ya da eğitimlerde görebilirsiniz. Şahsen kullanmanızı önermiyorum. Eski yöntemlerin eski olmasının bir sebebi var. Neden kullanmamızı tavsiye ediyorum kısmına gelince, fake object’ler çok fazla kod kalabalığı yapması, istediğiniz her dönüş için method’lar yazmak zorunda olmanız şeklinde sıralayabilirim.

Örnek bir fake kullanımı
Fake object ile test yazmak

Neden Mock yapmaya ihtiyacımız var sorusu bana sorulduğu zaman cevaplarımı şöyle sıralıyorum;

  • Testlerin çalışma hızını arttırır.
  • Yavaş algoritmaları test etme hızını arttırır. (DB,Webservice vb.)
  • Ürünün tamamlanmayan bölümleri olmasına rağmen test yazma olanağı sağlar. Mesela daha DBA’niz db tablolarını açmamış ya da web service entegre edilmediği durumda sizi etkilemez.
  • Test okunurluğunu arttırır.
  • Developer eforunu azaltır.
  • Complexity azalır.
  • Test In Isolation daha rahat uygulanır.

“Unit test nedir, unit nedir” gibi sorular aklınıza takıldıysa serinin önceki yazılarından detaylı şekilde öğrenebilirsiniz.

Test prensipleri

Çok adı geçen Test In Isolation’ı açıklayarak devam etmek istiyorum. Bu prensip tek bir test içerisinde sadece o methodun ya da sadece tek bir davranışın (web response, method dönüşü vb.) istenilen şekilde çalıştığını test etmemizi şart koşar. Biz de bu prensiple yola çıkarak yazdığımız testlerin daha efektif olduğunu görebilirsiniz.

Test In Isolation

Moq.Net Nuget için;

Moq.Net Source Code ve Dokümanlar için;

Bu konuda okumalar yapıyorsanız çok duyacağınız diğer bir terim ise Test Doubles’dır. Bunu detaylı olarak açıklamayı aşağıdan görebilirsiniz.

“Test Double is a generic term for any case where you replace a production object for testing purposes.” Martin Fowler

https://martinfowler.com/bliki/TestDouble.html

Mock Framework seçimi

Ben neredeyse tüm projelerimde şu ana kadar Moq.Net kullandım. İhtiyaçlarımın tamamını karşıladığı için bunu tercih ediyorum. Yazının devamında vereceğim tüm örnekler bu framework ile olacak. Diğer frameworkleri de araştırabilirsiniz. Genel olarak bu framework’den daha iyi bir yapı vaad eden bir yapı görmedim. Zaten her framework aşağı yukarı aynı şeyleri yapabiliyor. Kullanım kolaylığı ve yeteneklerine bakınca bu framework yeterli. Siz de farklı bir framework kullanıyorsanız ya da farklı türde ihtiyaçlarınız varsa aşağıya yorum atın konuşalım.

Method Mock

Method mock yaparken karşınıza çıkarak 3 tane davranışı bilmeniz gerekli.

  1. MockBehavior.Strict
  2. MockBehavior.Loose
  3. MockBehavior.Default

Strict, mock’lu method çağrıldığında setup edilmemişse exception fırlatır.

Buradaki commentli kısım olmazsa strict config’inden dolayı hata fırlatır

Loose, hiç bir zaman exception fırlatmaz. Çağrı sonrası exception yerine return tipi ne ise default halini döner, yeni object oluşturmaz. Dönüş türünüz int ise default value “0” olduğu için 0 döner. Eğer bir class ise null döner.

Default, default hali loose’dur

Loose vs. Strict

Loose daha az setup kodu gerektirir.

Loose setup olmasa bile default döner, strict’te ise her methodu mocklamanız gerekir.

Loose daha az hata verir, strict daha fazla hata verir.

Loose ile mevcut testleriniz patlamaz, strict ile çok sayıda hata alırsınız.

Bilmeniz gereken diğer bir yapı “It” class’ıdır. Bu class methoda yollanması gereken parametreler yerine daha geniş bir parametre kullanımına olanak verir. “Nasıl yani?” diye sormanız çok doğal. Bir örnek yüzlerce satır yazıdan daha iyidir diye düşünerek aşağıdaki örneklerle daha net anlayacaksınız.

Mock yapmadan yazılmış bir test

Yukarıdaki örnekte test yazmak çok basit olabilir. Ancak CreditNoteEvaluator class’ı external bir service kullansaydı bu o kadar kolay olmayacaktı.

It.IsAny<string> methodu ile herhangi bir string değeri için true döndürebiliyoruz.

It sayesinde böyle bir esneklikle test yazabiliyoruz. Eğer It kullanmazsak belirli bir value için çalışır ve sadece o değer geldiğinde belirtilen değeri döner diğer zamanlarda config değerine göre exception atar.

Method Verify / Not Verify

Moq framework’ü içerisinde yer alan bu method genel olarak assert yerine kullanılabilir. Verify ettiğiniz mock’lu methodlarınızın çağrılıp çağrılmadığını kontrol edersiniz. Eğer çağrılmazsa exception fırlatır. Bu şekilde mutlaka çağrılması gereken methodlarınızı kontrol edersiniz. Conditional yapılarınız varsa bunların doğru şekilde çalıştığını test etmek için çok idealdir. Diğer ideal kullanımlardan biri external service’lerinizi (db, orm, web service vb.) çağrıldığından emin olmak için kullanabilirsiniz. Yazıyla anlatacak olursak, yeni müşteri geldiğinde kayıt için ilgili service method’unun çağrıldığını verify edebilirsiniz.

SaveAsUnstructured() çağrılmazsa verify methodu exception fırlatır

Methodun çağrılmadığından emin olmak için de aynı mantıkla kontrol sağlayabilirsiniz. Bu şekilde db’ye 2 kere kayıt atmamanızı teyit edebilirsiniz.

Verify içerisindeki Times parametreli çalışan overload method ile çağrılmadığını kontrol edebilirsiniz

Property Getter/Setter

Moq.Net’in en sevdiğim özelliklerinden biri Getter/Setter çağrılma kontrolleridir. Benim gibi DDD yapıyorsanız veya çok fazla pattern kullanıyorsanız. Property’ler bağlı olduğu class içerisinde bir aksiyon olduğunda değişikliğe uğruyor olabilir. Bundan emin olmak için bu kontrolü kullanmak çok işlevseldir. Aynı zamanda söz konusu property’leri kullandığından emin olmak için kullanım yapmak ciddi bir avantaj sağlayacaktır.

Application Evaluate ederken IsValid Property’si kontrol edildiğinden bu şekilde emin olabiliriz.
Aynı basitlikte property set çağrıldığı mıkontrol ediyoruz. Değerini kontrol etmek başka bir testin konusu :)

Verify Methodlarını bir adım ileri taşımak

Verify methodunu çok fazla kullandığımı söylemeliyim. Verify’ın yetenekleri sadece yukarıda bahsettiğim kadar değil. Temel kullanımının yanına bir kaç özellik daha ekleyerek mükemmeleştirilmiş durumda. Bunlardan bir tanesi method çağrı sayılarını (method call count) kontrol edebiliyor olmasıdır.

Times kullanılarak çağrı count kontrolü yapılıyor. Email methodu içinde gelen request kadar email attığı Exactly() ile kontrol edilebilir.

Times parametreleri;

Never, Once, AtLeast, AtLeastOnce, AtMost, AtMostOnce, Between, Exactly

En fazla 1 kere çağırması için AtMostOnce ya da Exactly(1) gibi kontrollerle yapabilirsiniz.

Setup’ın efektif kullanımları

Şu ana kadar verdiğim örneklerde çok fazla setup kullanımı gördük. Bu setup’lar istediğiniz object’i dönebileceğini anlamışsınızdır. Bunu biraz daha ileriye taşıyarak async methodları da buradan dönüşünü ayarlayabilirsiniz.

Task.FromResult ile async return kullanılabilir.
Alternatif olarak ReturnsAsync methodunu da kullanabilirsiniz.

SetupAllProperties() methoduyla uzun satırlar yazmadan tüm property’lerinizi ayarlayabilirsiniz. Bu methodun source koduna bakınca başarılı bir reflection kullanımı ile tüm property’leri ayarladığını görebilirsiniz.

Setup etmediğimiz bir property’i kullanarak kontrol edebiliyoruz.

Ek mock teknikleri

Şu ana kadar gösterdiğim örnekler standart bir projede çok büyük oranda ihtiyacınızı karşılayacaktır. Ama bazen öyle durumlar oluyor ki, bunu nasıl mock’layacağım, ya da kompleks yapıları nasıl izole edip test edeceğim diye düşüncelere dalıyoruz. Bu tip durumlarda yani edge case adını verdiğimiz durumlarda Moq.Net bizi yalnız bırakmıyor. Linq’dan exception kontrollerine kadar hatta çoklu method çağrılarında istenilen aksiyonun yapılmasına kadar geniş bir spektrumda test coverage arttırmak için yapılar sunuyor.

Detaylandırmadan önce kısaca sıralarsak,

  • Mock objectlerden exception fırlatmak
  • Mock objectlerden event raise (otomatik/manuel)
  • Sıralı method çağrısı için setup
  • Value type’ları mock’lamak
  • Virtual protected methodları mock’lamak
  • Linq to mock
  • Moq factory kullanımı

Mock object’lerden exception fırlatmak

İlk başta böyle saçma bir şeye neden ihtiyacımız var diye düşünmüştüm. Zamanla farkettim ki bu da bilmeniz gereken şeylerden biriymiş; çünkü an geliyor ki bu tip kullanıma ihtiyaç duyuyorsunuz. Çok daha temiz bir şekilde belirli durumları kontrol edebiliyorsunuz. Ben bunu şu ana kadar tek bir amaçla kullandım. O da belirli bir parametre gelmesi durumunda exception fırlattığından emin olmak. Exception fırlattığında farklı bir aksiyon aldığım durumlar vardı ve exception durumunda belirli bir property’de ve dönüş değerinde doğru değerleri yakalamaktı. Bu durum API’nize request attığınızda BadRequest() ya da Middleware seviyesinde bir aksiyon aldığınızı teyit etmeniz için çok temiz ve basit bir kullanım.

Entegre olduğumuz banka servisine ulaşılamıyor ise banka şubelerine yönlendirme kısmına yollaması gerekli. Burada hata olduğunda branchflow statusu kontrol ediliyor.

Arkadaki CreditApplication’a bağlı kodda bank web servisi exception fırlattığında bu şekilde dönüş yapacak şekilde kurgulandı. Eğer öyle yapılmasaydı bu test esnasında da ilgili noktada exception fırlatacak ve kod çalışmayacaktı.

Mock object’lerden event raise

Event driven kod yazdığınız ya da kodunuzda event kullandığınızda event raise etmeniz gerekiyor. Bu durumlarda Moq.Net kolay bir kullanımla test edebilmenizi sağlıyor. Genel olarak pub/sub yapılarımda event raise etmeyi tercih ediyorum. Bu durumlarda doğru handler’larımın doğru şekilde çalıştığını daha geniş bir coverage ile sağlayabiliyorum.

Böyle bir durum oluştuğunda alarm sistemlerini tetikleyen queue’ya message yazdığını teyit ediyoruz.

Sıralı method çağrısı için setup

Bir methodu çağırıdığımızda farklı zamanlarda farklı datalar verebilir ve bizim kodumuz her zaman aynı çıktıyı üretmesi beklenebilir. Bunu test edebilmek çok önemli. Tam bu durumlar için Moq.Net bize SetupSequence() methoduyla yardımımıza koşuyor. Bu method olmasaydı inanılmaz zorlayıcı işlemler yapmak zorunda kalacaktık. Bana göre katma değeri en yüksek method budur.

ReturnSequence sayesinde çok sayıda returns ile sequence çağrıları test edebiliyoruz.

Value type’ları mock’lamak

Value type’lar bir default value içeriyor ama bizim bunları mock’lamamız gereken çok fazla durum var. En barizlerinden biri DateTime. Belirli bir günde veya saatte farklı davranması gereken method’lar yazmak zorunda kalabiliyoruz. Her ne kadar sevmesek de sabah saat 9'da mail listemize bülten yollamamız gerekli ve bu saat kontrolü bir noktada yapılmak zorunda. Bu durumda bir noktada o if kodu yazılmak zorunda kalınıyor. DateTime kendi içerisinde bir değer tuttuğu için bu değerleri değiştirmek pek mümkün değil. Aslında burada anlatacağım yöntem tek başına Moq.Net’in becerisi değil ama ciddi bir fayda sağladığı için buraya eklemeye karar verdim. Bu yöntem aynı zamanda ileri seviye unit test yazımın içeriğinde de olacak.

Yukarıdaki kodu nasıl test ederiz? Sadece cuma günleri doğru çalışan test yazmak çok mantıklı görünmedi değil mi?

Bu yöntem için en önemli anahtar kelime(keyword) abstraction. Datetime vb. kullanımları abstract ederek dönüş türlerini mock’layıp istediğimiz durum hakkında test yazabiliriz.

Biraz uzun olsa da istediğimizi bu şekilde elde edebiliyoruz

Virtual protected method mock’lamak

Güzel bir OOP kurgusunda çok fazla kullandığımız protected ve virtual keywordleri bize bazı durumlarda inanılmaz zorluklar yaşatıyor. Bunlardan ilki test yazmak. Dışarıdan ulaşamadığımız bu methodları mock’lamak için ileri seviye reflection kullanarak bir yöntem geliştirilmiş. Bunu tek bir method ile yapabiliyoruz o da Protected(). Bu sayede inherit ettiğimiz parent/super class’tan gelen method dönüş değerlerinde oynamalar yaparak çok fazla durumu (case) test edebiliyoruz. Bunu yapmadan da test yazabiliyoruz diyebilirsiniz. Buradaki temel amaç daha fazla değer aralığı için method’unuzun çalışmasını kontrol etmek. Test yazmanın temel mantığı herhangi bir methoddan herhangi bir dönüş geldiğinde bile istenildiği gibi çalıştığının kontrol edilmesi olduğu için bu kullanım önem arz ediyor.

Protected() ile her sınıfın protected’larını mock’layabilirsiniz.

Linq to Mock

Bunu görünce kafanızda değişik şeyler canlanabilir. Temel amaç test yazarken kodun temiz olmasıdır. Linq to mock hakkında denebilecek başka bir şey yok. Aşağıdaki örnekten de görüldüğü üzere daha kısa daha temiz ve anlaşılır test kodu yazabiliyoruz. Testlerdeki temel amaçlardan bir taneside test kodunun okunabilirliği olduğunu unutmamak gerekli. Bu açıdan faydalı bulduğum yöntemlerden biri.

Moq Factory

Moq kendi içerisinde factory’lere sahip. Bu yapının bize sunduğu temel fayda, merkezi bir configuration yaparak birden fazla setup kullanımında daha kısa kod ve okunabilir test kodu yazmak, Partial mock ile isolation prensibini daha iyi uygulamak, MockBehavior’u daha iyi yönetmek şeklinde sıralanabilir.

CallBase : Herşeyi mock’lamadan sadece belirli yerleri methodları mock’lamak için kullanılan temel object. Kısaca partial mock yapabilmek için faydalı.

DefaultValue : AutoMock ya da her seferinde belirli bir değeri dönmek için kullanılan method

Contructor : Yukarıda bahsettiğim 3 temel behavior’u belirterek. Tekrar tekrar aynı kodu yazmadan object mock’layabilmeniz için kullanılıyor.

Bu yazıda mock mantığını Moq.Net framework’ünden faydalanarak örnekler ile göstermeye çalıştım. Siz de bu şekilde testlerinizi daha efektif yazabilirsiniz. Eğer bu yapıları kullanıyorsanız ya da kullanmayı düşünüyorsanız ama sorularınız varsa bana LinkedIn, Twitter veya bu yazının altına yorumla ulaşabilirsiniz, elimden geldiğince yardımcı olmaya çalışırım.

Saygılarımla.

--

--

Orhun Begendi

Senior Enginner, Tech Lead, Hardcore Developer, Software Craftsman.