سؤال

خذ بعين الاعتبار طريقة في تجميع .NET:

public static string GetSecurityContextUserName()
{             
 //extract the username from request              
 string sUser = HttpContext.Current.User.Identity.Name;
 //everything after the domain     
 sUser = sUser.Substring(sUser.IndexOf("\\") + 1).ToLower();

 return sUser;      
}

أرغب في استدعاء هذه الطريقة من اختبار الوحدة باستخدام إطار عمل Moq.يعد هذا التجميع جزءًا من حل نماذج الويب.يبدو اختبار الوحدة هكذا، لكني أفتقد رمز Moq.

//arrange 
 string ADAccount = "BUGSBUNNY";
 string fullADName = "LOONEYTUNES\BUGSBUNNY"; 

 //act    
 //need to mock up the HttpContext here somehow -- using Moq.
 string foundUserName = MyIdentityBL.GetSecurityContextUserName();

 //assert
 Assert.AreEqual(foundUserName, ADAccount, true, "Should have been the same User Identity.");

سؤال:

  • كيف يمكنني استخدام Moq لترتيب كائن HttpContext مزيف بقيمة معينة مثل "MyDomain\MyUser"؟
  • كيف يمكنني ربط هذا المزيف بمكالمتي في طريقتي الثابتة في MyIdentityBL.GetSecurityContextUserName()?
  • هل لديك أي اقتراحات حول كيفية تحسين هذا الكود/البنية؟
هل كانت مفيدة؟

المحلول

من المعروف أن نماذج الويب غير قابلة للاختبار لهذا السبب بالتحديد - يمكن أن يعتمد الكثير من التعليمات البرمجية على الفئات الثابتة في مسار asp.net.

من أجل اختبار ذلك مع Moq، تحتاج إلى إعادة بناء ملفك GetSecurityContextUserName() طريقة لاستخدام حقن التبعية مع HttpContextBase هدف.

HttpContextWrapper يقيم في System.Web.Abstractions, ، والذي يأتي مع .Net 3.5.وهو عبارة عن غلاف ل HttpContext الطبقة، ويمتد HttpContextBase, ، ويمكنك إنشاء HttpContextWrapper تماما مثل هذا:

var wrapper = new HttpContextWrapper(HttpContext.Current);

والأفضل من ذلك، يمكنك الاستهزاء بـ HttpContextBase وتحديد توقعاتك عليه باستخدام Moq.بما في ذلك المستخدم الذي قام بتسجيل الدخول، وما إلى ذلك.

var mockContext = new Mock<HttpContextBase>();

مع هذا في مكانه، يمكنك الاتصال GetSecurityContextUserName(mockContext.Object), ، ويكون تطبيقك أقل اقترانًا بـ WebForms HttpContext الثابت.إذا كنت ستجري الكثير من الاختبارات التي تعتمد على سياق ساخر، فأنا أقترح ذلك بشدة إلقاء نظرة على فئة MvcMockHelpers الخاصة بسكوت هانسيلمان, ، والذي يحتوي على نسخة للاستخدام مع Moq.إنه يتعامل بسهولة مع الكثير من الإعدادات الضرورية.وعلى الرغم من الاسم، فإنك لا تحتاج إلى القيام بذلك باستخدام MVC - فأنا أستخدمه بنجاح مع تطبيقات نماذج الويب عندما أستطيع إعادة تصميمها لاستخدامها HttpContextBase.

نصائح أخرى

بشكل عام بالنسبة لاختبار وحدة ASP.NET، بدلاً من الوصول إلى HttpContext.Current، يجب أن يكون لديك خاصية من النوع HttpContextBase التي يتم تعيين قيمتها عن طريق حقن التبعية (كما هو الحال في الإجابة المقدمة من Womp).

ومع ذلك، لاختبار الوظائف المتعلقة بالأمان، أوصي باستخدام Thread.CurrentThread.Principal (بدلاً من HttpContext.Current.User).يتمتع استخدام Thread.CurrentThread أيضًا بميزة إمكانية إعادة استخدامه خارج سياق الويب (ويعمل بنفس الطريقة في سياق الويب لأن إطار عمل ASP.NET يقوم دائمًا بتعيين القيمتين بنفس الطريقة).

لاختبار Thread.CurrentThread.Principal بعد ذلك، عادةً ما أستخدم فئة النطاق التي تقوم بتعيين Thread.CurrentThread على قيمة اختبار ثم يتم إعادة تعيينها عند التخلص منها:

using (new UserResetScope("LOONEYTUNES\BUGSBUNNY")) {
    // Put test here -- CurrentThread.Principal is reset when PrincipalScope is disposed
}

يتناسب هذا جيدًا مع مكون أمان .NET القياسي - حيث يكون للمكون واجهة معروفة (IPrincipal) وموقع (Thread.CurrentThread.Principal) - وسيعمل مع أي كود يستخدم/يتحقق بشكل صحيح من Thread.CurrentThread.Principal .

ستكون فئة النطاق الأساسية كما يلي (اضبطها حسب الضرورة لأشياء مثل إضافة الأدوار):

class UserResetScope : IDisposable {
    private IPrincipal originalUser;
    public UserResetScope(string newUserName) {
        originalUser = Thread.CurrentPrincipal;
        var newUser = new GenericPrincipal(new GenericIdentity(newUserName), new string[0]);
        Thread.CurrentPrincipal = newUser;
    }
    public IPrincipal OriginalUser { get { return this.originalUser; } }
    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    protected virtual void Dispose(bool disposing) {
        if (disposing) {
            Thread.CurrentPrincipal = originalUser;
        }
    }
}

البديل الآخر هو، بدلاً من استخدام موقع مكون الأمان القياسي، قم بكتابة تطبيقك لاستخدام تفاصيل الأمان المُدخلة، على سبيل المثال.قم بإضافة خاصية ISecurityContext باستخدام طريقة GetCurrentUser() أو ما شابه ذلك، ثم استخدم ذلك بشكل متسق عبر التطبيق الخاص بك - ولكن إذا كنت ستفعل ذلك في سياق تطبيق ويب، فمن الأفضل أن تستخدم السياق المحقون مسبقًا ، httpContextBase.

الق نظرة على هذاhttp://haacked.com/archive/2007/06/19/unit-tests-web-code-without-a-web-server-using-httpsimulator.aspx

باستخدام فئة httpSimulator، ستتمكن من تمرير HttpContext إلى المعالج

HttpSimulator sim = new HttpSimulator("/", @"C:\intepub\?")
.SimulateRequest(new Uri("http://localhost:54331/FileHandler.ashx?
ticket=" + myticket + "&fileName=" + path));

FileHandler fh = new FileHandler();
fh.ProcessRequest(HttpContext.Current);

يقوم HttpSimulator بتنفيذ ما نحتاجه للحصول على مثيل HttpContext.لذلك لا تحتاج إلى استخدام موك هنا.

[TestInitialize]
public void TestInit()
{
  HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
}

كما يمكنك موك مثل أدناه

var controllerContext = new Mock<ControllerContext>();
      controllerContext.SetupGet(p => p.HttpContext.Session["User"]).Returns(TestGetUser);
      controllerContext.SetupGet(p => p.HttpContext.Request.Url).Returns(new Uri("http://web1.ml.loc"));

إذا كنت تستخدم نموذج الأمان CLR (كما نفعل)، فستحتاج إلى استخدام بعض الوظائف الملخصة للحصول على المبدأ الحالي وتعيينه إذا كنت تريد السماح بالاختبار، واستخدامه عند الحصول على المبدأ الأساسي أو تعيينه.يتيح لك القيام بذلك الحصول على/تعيين المبدأ الأساسي أينما كان ذلك مناسبًا (عادةً على HttpContext على الويب، وعلى الموضوع الحالي في مكان آخر مثل اختبارات الوحدة).سيبدو هذا مثل:

public static IPrincipal GetCurrentPrincipal()
{
    return HttpContext.Current != null ?
        HttpContext.Current.User :
        Thread.CurrentThread.Principal;
}

public static void SetCurrentPrincipal(IPrincipal principal)
{
     if (HttpContext.Current != null) HttpContext.Current.User = principal'
     Thread.CurrentThread.Principal = principal;
}

إذا كنت تستخدم مبدأً مخصصًا، فيمكن دمجها بشكل جيد إلى حد ما في واجهته، على سبيل المثال أدناه Current سوف يتصل GetCurrentPrincipal و SetAsCurrent سوف يتصل SetCurrentPrincipal.

public class MyCustomPrincipal : IPrincipal
{
    public MyCustomPrincipal Current { get; }
    public bool HasCurrent { get; }
    public void SetAsCurrent();
}

لا يرتبط هذا حقًا باستخدام Moq لاختبار الوحدة لما تحتاجه.

بشكل عام، لدينا في العمل بنية متعددة الطبقات، حيث يكون الكود الموجود على طبقة العرض مخصصًا فقط لترتيب الأشياء ليتم عرضها على واجهة المستخدم.لا يتم تغطية هذا النوع من التعليمات البرمجية باختبارات الوحدة.كل ما تبقى من المنطق موجود في طبقة الأعمال، والتي لا يجب أن يكون لها أي تبعية على طبقة العرض التقديمي (أي طبقة العرض).مراجع محددة لواجهة المستخدم مثل HttpContext) نظرًا لأن واجهة المستخدم قد تكون أيضًا تطبيق WinForms وليست بالضرورة تطبيق ويب.

بهذه الطريقة يمكنك تجنب العبث بأطر عمل Mock، ومحاولة محاكاة HttpRequests وما إلى ذلك...على الرغم من أن ذلك قد يظل ضروريًا في كثير من الأحيان.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top