تنظیم خروجی متدهای اشیاء Mock شده
در انتهای قسمت قبل، آزمون واحد متد Accept، با شکست مواجه شد؛ چون متد Validate استفاده شده، همواره مقدار false را بر میگرداند:
_identityVerifier.Initialize(); var isValidIdentity = _identityVerifier.Validate( application.Applicant.Name, application.Applicant.Age, application.Applicant.Address);
در ادامه شیء Mock از نوع IIdentityVerifier را طوری تنظیم خواهیم کرد که بر اساس یک applicant مشخص، خروجی true را بازگشت دهد:
namespace Loans.Tests { [TestClass] public class LoanApplicationProcessorShould { [TestMethod] public void Accept() { var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m}; var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0}; var applicant = new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_500_000_0}; var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant}; var mockIdentityVerifier = new Mock<IIdentityVerifier>(); mockIdentityVerifier.Setup(x => x.Validate(applicant.Name, applicant.Age, applicant.Address)) .Returns(true); var mockCreditScorer = new Mock<ICreditScorer>(); var processor = new LoanApplicationProcessor(mockIdentityVerifier.Object, mockCreditScorer.Object); processor.Process(application); Assert.IsTrue(application.IsAccepted); } } }
به این ترتیب زمانیکه در متد Process کلاس LoanApplicationProcessor کار به بررسی هویت کاربر میرسد، اگر متد Validate آن با اطلاعات applicant مشخصی که تنظیم کردیم، یکی بود، متغیر isValidIdentity که حاصل بررسی identityVerifier.Validate_ است، به true مقدار دهی خواهد شد. برای بررسی آن یک break-point را در این نقطه قرار داده و آزمون واحد را در حالت دیباگ اجرا کنید.
البته هرچند اگر اکنون نیز این آزمایش واحد را مجددا بررسی کنیم، باز هم با شکست مواجه خواهد شد؛ چون مرحلهی بعدی بررسی، کار با سرویس ICreditScorer است که هنوز تنظیم نشدهاست:
_creditScorer.CalculateScore(application.Applicant.Name, application.Applicant.Address); if (_creditScorer.Score < MinimumCreditScore) { return application.IsAccepted; }
تطابق با آرگومانهای متدها در متدهای Mock شده
با تنظیمی که انجام دادیم، اگر متد Validate به مشخصات شیء applicant مشخص ما برسد، خروجی true را بازگشت میدهد. برای مثال اگر در این بین تنها نام شخص تغییر کند، خروجی بازگشت داده شده همان false خواهد بود. اما اگر این نام برای ما اهمیتی نداشت و قصد داشتیم با تمام نامهای متفاوتی که دریافت میکند، بازهم خروجی true را بازگشت دهد، میتوان از قابلیت argument matching کتابخانهی Moq و کلاس It آن استفاده کرد:
var mockIdentityVerifier = new Mock<IIdentityVerifier>(); mockIdentityVerifier.Setup(x => x.Validate( //applicant.Name, It.IsAny<string>(), applicant.Age, applicant.Address)) .Returns(true);
بدیهی است در این حالت باید سایر پارامترها دقیقا با مقادیر مشخص شده تطابق داشته باشند و اگر این موارد نیز اهمیتی نداشتند، میتوان به صورت زیر عمل کرد:
var mockIdentityVerifier = new Mock<IIdentityVerifier>(); mockIdentityVerifier.Setup(x => x.Validate( //applicant.Name, It.IsAny<string>(), //applicant.Age, It.IsAny<int>(), //applicant.Address It.IsAny<string>() )) .Returns(true);
البته این نوع تنظیمات بیشتر برای حالات غیرمشخص مانند استفادهاز Guidها به عنوان پارامترها و مقادیر، میتواند مفید باشد.
تقلید متدهایی که پارامترهایی از نوع out دارند
اگر به اینترفیس IIdentityVerifier که در قسمت قبل معرفی شد دقت کنیم، یکی از متدهای آن دارای خروجی از نوع out است:
using Loans.Models; namespace Loans.Services.Contracts { public interface IIdentityVerifier { void Validate(string applicantName, int applicantAge, string applicantAddress, out bool isValid); // ... } }
//var isValidIdentity = _identityVerifier.Validate( // application.Applicant.Name, application.Applicant.Age, application.Applicant.Address); _identityVerifier.Validate( application.Applicant.Name, application.Applicant.Age, application.Applicant.Address, out var isValidIdentity);
var isValidOutValue = true; mockIdentityVerifier.Setup(x => x.Validate(applicant.Name, applicant.Age, applicant.Address, out isValidOutValue));
اکنون اگر مجددا آزمون واحد متد Accept را اجرا کنیم، با موفقیت به پایان میرسد.
تقلید متدهایی که پارامترهایی از نوع ref دارند
اگر به اینترفیس IIdentityVerifier که در قسمت قبل معرفی شد دقت کنیم، یکی از متدهای آن دارای خروجی از نوع ref است:
using Loans.Models; namespace Loans.Services.Contracts { public interface IIdentityVerifier { void Validate(string applicantName, int applicantAge, string applicantAddress, ref IdentityVerificationStatus status); // ... } }
namespace Loans.Models { public class IdentityVerificationStatus { public bool Passed { get; set; } } }
IdentityVerificationStatus status = null; _identityVerifier.Validate( application.Applicant.Name, application.Applicant.Age, application.Applicant.Address, ref status); if (!status.Passed) { return application.IsAccepted; }
ابتدا در سطح کلاس آزمایش واحد یک delegate را تعریف میکنیم:
delegate void ValidateCallback(string applicantName, int applicantAge, string applicantAddress, ref IdentityVerificationStatus status);
اکنون روش استفادهی از آن برای برپایی تنظیمات mocking متد Validate از نوع ref دار به صورت زیر است:
mockIdentityVerifier .Setup(x => x.Validate(applicant.Name, applicant.Age, applicant.Address, ref It.Ref<IdentityVerificationStatus>.IsAny)) .Callback(new ValidateCallback( (string applicantName, int applicantAge, string applicantAddress, ref IdentityVerificationStatus status) => status = new IdentityVerificationStatus {Passed = true}));
سپس متد جدید Callback را مشاهده میکنید. توسط آن میتوان یک قطعه کد سفارشی را زمانیکه متد Mock شدهی Validate ما اجرا میشود، اجرا کرد. در اینجا delegate سفارشی ما اجرا شده و مقدار status را بر میگرداند؛ اما در ادامه این مقدار را به یک new IdentityVerificationStatus سفارشی تنظیم میکنیم که در آن مقدار خاصیت Passed، مساوی true است.
اکنون اگر مجددا آزمون واحد متد Accept را اجرا کنیم، با موفقیت به پایان میرسد.
تنظیم متدهای Mock شده جهت بازگشت null
فرض کنید اینترفیسی به صورت زیر تعریف شدهاست:
namespace Loans.Services.Contracts { public interface INullExample { string SomeMethod(); } }
namespace Loans.Tests { [TestClass] public class LoanApplicationProcessorShould { [TestMethod] public void NullReturnExample() { var mock = new Mock<INullExample>(); mock.Setup(x => x.SomeMethod()); //.Returns<string>(null); string mockReturnValue = mock.Object.SomeMethod(); Assert.IsNull(mockReturnValue); } } }
الف) میتوان همانند سابق متد Returns را ذکر کرد که نال بر میگرداند؛ اما با این تفاوت که حتما باید نوع آرگومان جنریک آنرا نیز بر اساس خروجی متد، مشخص کرد.
ب) کتابخانهی Moq، مقدار خروجی پیشفرض تمام متدهایی را که یک نوع ارجاعی را باز میگردانند، نال درنظر میگیرد و عملا نیازی به ذکر متد Returns در اینجا نیست.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MoqSeries-02.zip