Strict Mock

Birim Testleri - Strict Mock

Moq kütüphanesinin Mock sınıfları oluşturulurken varsayılan davranış şekli Loose'dur. Bu şekilde tanımlanan mock nesneleri metot çağrıları için kurulum zorunluluğu dayatmaz.

var loggingService = new Mock<ILoggingService>();
var loggingService = new Mock<ILoggingService>(MockBehavior.Loose);

Strict Mock bir mock nesnenin çalışması için setup işleminin yapılmasını zorunlu kılar.

var loggingService = new Mock<ILoggingService>(MockBehavior.Strict);

Yararlar

Bu kullanım aşağıdaki yararlara sahiptir:

1. Doğrulanması unutulan metot çağrılarını (Verify) tespit eder.

  • void metotların çalışıp çalışmadığı kontrol edilmelidir.

  • Strict Mock kullanılmadığı durumlarda bu kontrol ancak Verify çağrısı ile sağlanır.

  • Assert bölümünde Verify çağrısı unutulursa test edilen metodun tüm davranışları test edilmemiş olur.

  • Strict Mock her çağrılan mock nesne metodunun setup'ını zorunlu kıldığından bu metotların da setupları yapılır.

  • Setup'ı yapılıp çalışmayan mock nesne metodu aşağıdaki hatayı oluşturur.

Moq.MockException : Mock<ISecurityService:1>:
This mock failed verification due to the following:

   ISecurityService x => x.Encrypt():
   This setup was not matched.
  • Çalıştığı doğrulanmak istenen metot kurulumu için Verifiable() olarak işaretlenir.

securityService.Setup(x => x.Encrypt()).Verifiable();
  • Metotlarının kurulumları yapılan mock nesne Assert bölümünde Verify() metodu çağrısı ile doğrulanmalıdır.

securityService.Verify();
  • Metotların kurulumunda Verifiable çağrısı yapılmadan, mock nesnenin tüm çağrılarının (Verifiable veya değil) doğrulamasını sağlamak için VerifyAll() metodu kullanılır. (Önerilen pratik budur)

securityService.VerifyAll();

2. Çalışmaması gereken metot çağrılarını tespit eder.

  • Çağrılan her metodun setup'ı yapılmalıdır.

  • Setup'ı yapılmayan bir metot çağrıldığında aşağıdaki hata alınır:

Moq.MockException : ISecurityService.CreateHash("foo") invocation failed with mock behavior Strict.
All invocations on the mock must have a corresponding setup.

3. Assert bölümlerinde Verify işlemleri sadeleşir

  • Verify edilmesi gereken metotların kurulumları da Arrange bölümünde yapıldığından Assert bölümün daha sade ve anlaşılır hale gelir.

4. Metodun fazla sorumluluğu varsa bunu önplana çıkarır Doğrulanması gereken çağrılar için setup gerektiğinden, setup'ın ve test edilen metodun büyüklüğü gözle görülür hale gelir.

  • Büyük bir test kurulum bölümü, test edilen metodun gerektiğinden fazla sorumluluğa sahip olduğunun ve yeterince soyutlama yapılmadığının göstergesidir.

  • Extract Class veya Move Method yöntemleri uygulanabilir.

Uygulama Örneği

TestFixture sınıfları Foo.Framework.UnitTests v2.0 nuget paketinde bulunan BaseTestFixture sınıfından türetilir.

BaseTextFixture sınıfı

namespace Foo.Framework.UnitTests
{
    /// <summary>
    /// Fixture sınıfları için temel sınıf. Strict Mock kullanımını zorunlu kılar. Teardown metodunda VerifyMocks işlemi içerir.
    /// </summary>
    public abstract class BaseTestFixture : TestFixture
    {
        /// <summary>
        /// VerifyMocks metodunu tetikleyen işlemi. TestFixture'da tanımlanan tüm mock nesnelerin Verify edilmesi hedeflenir.
        /// </summary>
        [TearDown]
        public void TearDown()
        {
            VerifyMocks();
        }

        /// <summary>
        ///  TestFixture'da tanımlanan tüm mock nesnelerin Verify edilmesini hedefleyen soyut metot.
        ///  Mock nesnelerin VerifyAll() metodunun çağrılması beklenir.
        /// </summary>
        protected abstract void VerifyMocks();
    }
}

Örnek TestFixture tanımı

[TestFixture]
[Parallelizable(ParallelScope.Fixtures)]
public class CreditCardMailServiceTests : BaseTestFixture
{
    //TODO:
}

Testlerde kullanılan mock member bileşenleri aynı pakette bulunan StrictMock tipinde tanımlanır.

Örnek Setup metodu

#region members & setup

StrictMock<IMailServiceRepository> mailServiceRepository;

StrictMock<IMailInfoFactory> mailInfoFactory;

StrictMock<IFooRepository> fooRepository;

StrictMock<ISettings> settings;

CreditCardService service;

[SetUp]
public void Initialize()
{
    mailInfoFactory = new StrictMock<IMailInfoFactory>();
    mailServiceRepository = new StrictMock<IMailServiceRepository>();
    fooRepository = new StrictMock<IFooRepository>();
    settings = new StrictMock<ISettings>();

    service = new CreditCardService(
        mailInfoFactory.Object,
        mailServiceRepository.Object,
        fooRepository.Object,
        settings.Object);
}

#endregion

BaseTestFixture sınıfı her testten sonra çalışmak üzere TearDown metodunu içerir ve bu metotta VerifyMocks metodu çağrılır. BaseTestFixture sınıfında bulunan abstract VerifyMocks metodu implemente edilir ve TestFixture'da bulunan tüm mock memberlar için VerifyAll metodu çağrılır. Bu metot bir mock için yapılan setup işlemi Verifable olarak işaretlenip işaretlenmeme durumuna bakmaksızın mock nesne için Verify işlemini uygular.

Örnek VerifyMocks implementasyonu

#region verify mocks

protected override void VerifyMocks()
{
    settings.VerifyAll();
    mailServiceRepository.VerifyAll();
    mailInfoFactory.VerifyAll();
    fooRepository.VerifyAll();
    mailServiceRepository.VerifyAll();
}

#endregion

Testlerde Arrange bölümünde setup işlemlerini Verifiable olarak işaretlemek ve Assert bölümünde mocklar için Verify veya VerifyAll çağrısı yapmaya gerek yoktur.

Örnek Test

[Test]
public void SendCreditApplicationMailToMember_SendMailFails_ReturnServerError()
{
	// Arrange
	var creditApplication = new CreditApplication();
	var mailInfo = new MailInfo();
	const bool sendMailSuccess = false;

	mailInfoFactory.Setup(x => x.CreateCreditApplicationMailInfoForMember(creditApplication)).Returns(mailInfo);
	mailServiceRepository.Setup(x => x.SendMail(mailInfo)).Returns(sendMailSuccess);

	// Act
	var result = service.SendCreditApplicationMailToMember(creditApplication);

	// Assert
	result.IsSuccess.Should().BeFalse();
	result.ResultCode.Should().Be(ResultCode.ServerError);
}

Mock member içermeyen TestFixture sınıfları

Mock member içermeyen TestFixture sınıflarında mock Verify işlemine gerek olmadığından bu sınıflar BaseTestFixture yerine LooseBaseTestFixture sınıfından türetilirler. Bu sınıf bir TearDown metodu ve abstract VerifyMocks metodu içermez.

namespace Foo.Framework.UnitTests
{
    /// <summary>
    /// Strict Mock kullamının gerekmediği TestFixture sınıfları için temel sınıf.
    /// </summary>
    public class LooseBaseTestFixture : TestFixture
    {
    }
}
using Moq;

namespace Foo.Framework.UnitTests
{
    /// <summary>
    /// Birim testleri Fixture sınıfları için temel sınıf.
    /// </summary>
    public abstract class TestFixture
    {
        #region verify any

        /// <summary>
        /// Moq.Mock sınıfları için Verify işlemi uygulandığında geçirilen It.IsAny tipinde parametreler için kullanılır.
        /// It.IsAny metodunun hangi bağlamda kullanıldığını tespit etmek amacıyla oluşturulmuştur.
        /// </summary>
        /// <typeparam name="T">Any olarak kullanılacak tip bilgisi</typeparam>
        /// <returns></returns>
        public static T VerifyAny<T>()
        {
            return Any<T>();
        }

        #endregion

        #region setup any

        /// <summary>
        /// Moq.Mock sınıfları için Setup işlemi uygulandığında geçirilen It.IsAny tipinde parametreler için kullanılır.
        /// It.IsAny metodunun hangi bağlamda kullanıldığını tespit etmek amacıyla oluşturulmuştur.
        /// </summary>
        /// <typeparam name="T">Any olarak kullanılacak tip bilgisi</typeparam>
        /// <returns></returns>
        public static T SetupAny<T>()
        {
            return Any<T>();
        }

        static T Any<T>()
        {
            return It.IsAny<T>();
        }

        #endregion
    }
}

Örnek Mock içermeyen TestFixture

[TestFixture]
[Parallelizable(ParallelScope.Fixtures)]
public class CreditCardFactoryTests : LooseBaseTestFixture
{
    //TODO:
}

Last updated