شاید شما هم در پروژه خودتان نیاز داشته باشید تا اتصالات MediatR را بررسی یا به نوعی از صحت کدهایی که بر پایه MediatR زدید مطمئن شوید. در اینجا به بررسی نحوه Assert کردن اتصالات MediatR میپردازم. اول از همه باید به این فکر کرد که چه چیزی را میخواهیم تست کنیم؟ طبیعتا وقتی یک Command را ایجاد میکنیم، انتظار داریم که یک CommandHandler مختص به آن نیز ایجاد شده باشد. پس ما به بررسی ساختار کد میپردازیم.
برای شروع، یک Interface را با متد IsValid را ایجاد میکنیم. این Interface بعدها توسط کلاس CommandValidator پیاده سازی میشود تا هر وظیفه، تشخیص درست بودن اتصالات command را چک کنند.
همانطور که مشخص است، متد IsValid دو پارامتر اسم ارسال کننده (Command,Query,Notification) و اسم Handler این ارسال کنندهها را دریافت میکند.
internal interface IValidation
{
bool IsValid(string sendNamesEndTo, string handlerNamesEndTo);
}
کد زیر نحوه پیاده سازی IValidation را برای کلاس CommandValidator، نشان میدهد:
public bool IsValid(string commandNamesEndTo = "Command", string commandHandlersEndTo = "CommandHandler")
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var commandTypeInfos = assemblies.SelectMany(x => x.DefinedTypes.Where(typeInfo =>
typeInfo.Name.ToLower().EndsWith(commandNamesEndTo.ToLower()) &&
typeInfo.ImplementedInterfaces.Any(type => type == typeof(IBaseRequest))));
var memberInfos = commandTypeInfos as TypeInfo[] ?? commandTypeInfos.ToArray();
if (!memberInfos.Any())
throw new ArgumentException("Can not find any Command");
var handlerTypeInfo = assemblies.SelectMany(x => x.DefinedTypes.Where(typeInfo =>
typeInfo.Name.ToLower().EndsWith(commandHandlersEndTo.ToLower())));
var typeInfos = handlerTypeInfo as TypeInfo[] ?? handlerTypeInfo.ToArray();
if (!typeInfos.Any())
throw new ArgumentException("Can not find any CommandHandler");
if (typeInfos.Count() != memberInfos.Count())
return false;
return !(from typeInfo in memberInfos
let interfaces = typeInfos.SelectMany(x => x.ImplementedInterfaces)
where interfaces.Any(x => x.GenericTypeArguments.All(type => type != typeInfo))
select typeInfo).Any();
}
همانطور که مشخص است، برای دو پارامتر ورودی، مقادیر پیش فرض Command و CommandHandler در نظر گرفته شدهاند. توجه داشته باشید این اسم کامل نیست و تنها نام انتهای کلاس است: برای مثال OrderCommand/OrderCommandHandler:
public bool IsValid(string commandNamesEndTo = "Command", string commandHandlersEndTo = "CommandHandler")
داخل بدنه متد، ابتدا تمام Assemblyهای موجود در App را لیست میکنیم :
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
سپس تمام کلاسهای تعریف شدهای را که از IBaseRequest ارث بری کرده و انتهای نام آنها شامل commandNamesEndTo باشد، لیست میکند. میتوان گفت تا اینجا تمام Commandها را پیدا کردهایم. در صورتیکه لیست موجود خالی باشد، یعنی یکجای کار مشکل دارد؛ شاید کلا Command ای تعریف نشده یا ... پس یک ArgumentException را throw میکنیم.
var commandTypeInfos = assemblies.SelectMany(x => x.DefinedTypes.Where(typeInfo =>
typeInfo.Name.ToLower().EndsWith(commandNamesEndTo.ToLower()) &&
typeInfo.ImplementedInterfaces.Any(type => type == typeof(IBaseRequest))));
var memberInfos = commandTypeInfos as TypeInfo[] ?? commandTypeInfos.ToArray();
if (!memberInfos.Any())
throw new ArgumentException("Can not find any Command");
در مرحلهی بعدی، تمام Handlerها را پیدا و لیست میکنیم. در اینجا نیز مانند کد بالا، اگر لیست خالی باشد، به این معناست که هیچ handler ای تعریف نشدهاست.
var handlerTypeInfo = assemblies.SelectMany(x => x.DefinedTypes.Where(typeInfo =>
typeInfo.Name.ToLower().EndsWith(commandHandlersEndTo.ToLower())));
var typeInfos = handlerTypeInfo as TypeInfo[] ?? handlerTypeInfo.ToArray();
if (!typeInfos.Any())
throw new ArgumentException("Can not find any CommandHandler");
مرحله بعدی، بررسی کردن تعداد Commandها و CommandHandlerها میباشد. طبیعتا باید مقدار این دو برابر باشند؛ چرا که هر Command، نیاز به یک CommandHandler نیز دارد. بنابراین اگر تعداد این دو یکسان نبود، ساختار ما درست نیست؛ پس مقدار false برگشت داده میشود :
if (typeInfos.Count() != memberInfos.Count())
return false;
بعد از تمام این ماجراها، به مرحله آخر میرسیم؛ آن هم چک کردن نظیر به نظیر Commandها با CommandHandlerها میباشد. به این صورت که اگر به ازای هر Command تعریف شده یک CommandHandler با GenericTypeArguments از نوع command وجود داشته باشد، میتوان گفت که ساختار ما درست تعریف شدهاست.
return !(from typeInfo in memberInfos
let interfaces = typeInfos.SelectMany(x => x.ImplementedInterfaces)
where interfaces.Any(x => x.GenericTypeArguments.All(type => type != typeInfo))
select typeInfo).Any();
توجه داشته باشید برای تست کردن قسمتهایی مثل Query و Notification نیز میتوان از همین فرآیند بهره برد؛ البته با کمی تغییر در پیاده سازی متد IsValid.
در نهایت برای استفاده میتوان به شکل زیر کدها را استفاده کرد :
var validCommandConfiguration = new CommandValidator().IsValid();
برای دیدن کدهای کامل پیاده سازی تست Command/Query/Notification، میتوانید از لینک گیت هاب زیر استفاده کنید.