2013-10-27 7 views
26

मैं (के रूप में ट्यूटोरियल Create an ASP.NET MVC 5 App with Facebook and Google OAuth2 and OpenID Sign-on (C#) में दिखाया गया है) एक नया संपत्ति जोड़कर ApplicationUser वर्ग का विस्तार कर रहा हूँयूनिट टेस्टिंग ASP.NET MVC5 अनुप्रयोग

public class ApplicationUser : IdentityUser 
{ 
    public DateTime BirthDate { get; set; } 
} 

अब मुझे लगता है कि सत्यापित करने के लिए एक यूनिट टेस्ट बनाना चाहते हैं मेरे खाता नियंत्रक सही ढंग से जन्मदिन को सहेज रहा है।

मैं एक में स्मृति उपयोगकर्ता की दुकान controller.Register विधि संदर्भ उद्देश्यों के लिए मैं इसे यहाँ शामिल कर रहा हूँ MVC5 द्वारा उत्पन्न बॉयलरप्लेट कोड है, लेकिन TestUserStore

[TestMethod] 
public void Register() 
{ 
    // Arrange 
    var userManager = new UserManager<ApplicationUser>(new TestUserStore<ApplicationUser>()); 
    var controller = new AccountController(userManager); 

    // This will setup a fake HttpContext using Moq 
    controller.SetFakeControllerContext(); 

    // Act 
    var result = 
     controller.Register(new RegisterViewModel 
     { 
      BirthDate = TestBirthDate, 
      UserName = TestUser, 
      Password = TestUserPassword, 
      ConfirmPassword = TestUserPassword 
     }).Result; 

    // Assert 
    Assert.IsNotNull(result); 

    var addedUser = userManager.FindByName(TestUser); 
    Assert.IsNotNull(addedUser); 
    Assert.AreEqual(TestBirthDate, addedUser.BirthDate); 
} 

नामित बना लिया है।

// POST: /Account/Register 
[HttpPost] 
[AllowAnonymous] 
[ValidateAntiForgeryToken] 
public async Task<ActionResult> Register(RegisterViewModel model) 
{ 
    if (ModelState.IsValid) 
    { 
     var user = new ApplicationUser() { UserName = model.UserName, BirthDate = model.BirthDate }; 
     var result = await UserManager.CreateAsync(user, model.Password); 
     if (result.Succeeded) 
     { 
      await SignInAsync(user, isPersistent: false); 
      return RedirectToAction("Index", "Home"); 
     } 
     else 
     { 
      AddErrors(result); 
     } 
    } 

    // If we got this far, something failed, redisplay form 
    return View(model); 
} 

जब मैं रजिस्टर कहता हूं, तो यह साइनइनएसिंक को कॉल करता है, जहां समस्या होगी।

private async Task SignInAsync(ApplicationUser user, bool isPersistent) 
{ 
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie); 
    var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie); 
    AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity); 
} 

निम्नतम स्तर पर, बॉयलरप्लेट कोड इस tidbit

private IAuthenticationManager AuthenticationManager 
{ 
    get 
    { 
     return HttpContext.GetOwinContext().Authentication; 
    } 
} 

यह वह जगह है जहां problm की जड़ होती है भी शामिल है। GetOwinContext को यह कॉल एक एक्सटेंशन विधि है जिसे मैं नकल नहीं कर सकता और मैं स्टब के साथ प्रतिस्थापित नहीं कर सकता (जब तक कि मैं बॉयलरप्लेट कोड नहीं बदलता)।

जब मैं इस परीक्षण चलाने मैं एक अपवाद

Test method MVCLabMigration.Tests.Controllers.AccountControllerTest.Register threw exception: 
System.AggregateException: One or more errors occurred. ---> System.NullReferenceException: Object reference not set to an instance of an object. 
at System.Web.HttpContextBaseExtensions.GetOwinEnvironment(HttpContextBase context) 
at System.Web.HttpContextBaseExtensions.GetOwinContext(HttpContextBase context) 
at MVCLabMigration.Controllers.AccountController.get_AuthenticationManager() in AccountController.cs: line 330 
at MVCLabMigration.Controllers.AccountController.<SignInAsync>d__40.MoveNext() in AccountController.cs: line 336 

पूर्व विज्ञप्ति में मिल ASP.NET MVC टीम कोड परीक्षण योग्य बनाने के लिए बहुत मेहनत की है। ऐसा लगता है कि अब एक खाता नियंत्रक का परीक्षण करना आसान नहीं होगा। मेरे पास कुछ विकल्प हैं।

मैं

  1. बॉयलर प्लेट कोड को संशोधित कर सकते हैं इतना है कि यह एक विस्तार विधि कॉल नहीं करता है और उस स्तर पर इस समस्या से निपटने के

  2. सेटअप परीक्षण प्रयोजनों के लिए Owin पाइपलाइन

  3. लेखन कोड लिखने से बचें जिसके लिए ऑथन/ऑथज़ इंफ्रास्ट्रक्चर (उचित विकल्प नहीं)

मुझे यकीन नहीं है कि कौन सी सड़क बेहतर है। कोई भी इसे हल कर सकता है। मेरा सवाल नीचे उबलता है जो सबसे अच्छी रणनीति है।

नोट: हाँ, मुझे पता है कि मुझे उस कोड का परीक्षण करने की आवश्यकता नहीं है जिसे मैंने नहीं लिखा था। UserManager आधारभूत संरचना प्रदान की गई एमवीसी 5 बुनियादी ढांचे का एक टुकड़ा है लेकिन यदि मैं उन परीक्षणों को लिखना चाहता हूं जो एप्लिकेशनयूसर या कोड में मेरे संशोधन को सत्यापित करते हैं जो उपयोगकर्ता की भूमिकाओं पर निर्भर व्यवहार की पुष्टि करता है तो मुझे UserManager का उपयोग करके परीक्षण करना होगा।

+0

हाय क्या आप कृपया बताए गए ट्यूटोरियल को इंगित कर सकते हैं। अगर आप रजिस्टर विधि के कार्यान्वयन के साथ अपना प्रश्न अपडेट कर सकते हैं तो यह सहायक भी होगा। – Spock

+0

आपके यूनिट परीक्षणों के संदर्भ में HttpContext शून्य होगा। आपके द्वारा उपयोग की जाने वाली संपत्तियों के मानों को वापस करने के लिए आपको एक नकली HttpContext ऑब्जेक्ट इंजेक्ट करने की आवश्यकता हो सकती है। –

+0

मेरे पास एक नकली सेटअप था - ऊपर नमूना कोड में नहीं था लेकिन मैंने इसे अभी जोड़ा है। –

उत्तर

26

मैं अपने स्वयं के प्रश्न का उत्तर दे रहा हूं इसलिए यदि आपको लगता है कि यह एक अच्छा जवाब है तो मैं समुदाय से एक समझ प्राप्त कर सकता हूं।

चरण 1: जेनरेट किए गए खाता नियंत्रक को बैकिंग फ़ील्ड का उपयोग करके प्रमाणीकरण प्रबंधक के लिए एक संपत्ति सेटर प्रदान करने के लिए संशोधित करें।

// Add this private variable 
private IAuthenticationManager _authnManager; 

// Modified this from private to public and add the setter 
public IAuthenticationManager AuthenticationManager 
{ 
    get 
    { 
     if (_authnManager == null) 
      _authnManager = HttpContext.GetOwinContext().Authentication; 
     return _authnManager; 
    } 
    set { _authnManager = value; } 
} 

चरण 2: Microsoft.OWin.IAuthenticationManager इंटरफ़ेस

[TestMethod] 
public void Register() 
{ 
    // Arrange 
    var userManager = new UserManager<ApplicationUser>(new TestUserStore<ApplicationUser>()); 
    var controller = new AccountController(userManager); 
    controller.SetFakeControllerContext(); 

    // Modify the test to setup a mock IAuthenticationManager 
    var mockAuthenticationManager = new Mock<IAuthenticationManager>(); 
    mockAuthenticationManager.Setup(am => am.SignOut()); 
    mockAuthenticationManager.Setup(am => am.SignIn()); 

    // Add it to the controller - this is why you have to make a public setter 
    controller.AuthenticationManager = mockAuthenticationManager.Object; 

    // Act 
    var result = 
     controller.Register(new RegisterViewModel 
     { 
      BirthDate = TestBirthDate, 
      UserName = TestUser, 
      Password = TestUserPassword, 
      ConfirmPassword = TestUserPassword 
     }).Result; 

    // Assert 
    Assert.IsNotNull(result); 

    var addedUser = userManager.FindByName(TestUser); 
    Assert.IsNotNull(addedUser); 
    Assert.AreEqual(TestBirthDate, addedUser.BirthDate); 
} 

अब परीक्षण में सफ़ल के लिए एक नकली जोड़ने के लिए इकाई परीक्षण को संशोधित करें।

अच्छा विचार? बुरा विचार?

+4

मैं शायद 'IAuthenticationManager' को एक निजी रीडोनली फ़ील्ड बनाउंगा और इसे कन्स्ट्रक्टर के माध्यम से सेट कर दूंगा। –

+0

यह सिर्फ एक बहुत ही छोटा बदलाव है, लेकिन प्रमाणीकरण प्रबंधक की संपत्ति प्राप्त करने के लिए लिखा जा सकता है ??, इस प्रकार शून्य की जांच अनावश्यक है। – viniciushana

+1

नकली क्या है? मैं इसका उपयोग कैसे कर सकता हूं? –

3

मैंने आपके जैसे समाधान का उपयोग किया है - IAuthenticationManager का मज़ाक उड़ा रहा है - लेकिन मेरा लॉगिन कोड एक लॉगिन प्रबंधक श्रेणी में है जो IAuthenticationManager को कन्स्ट्रक्टर इंजेक्शन के माध्यम से लेता है।

public LoginHandler(HttpContextBase httpContext, IAuthenticationManager authManager) 
    { 
     _httpContext = httpContext; 
     _authManager = authManager; 
    } 

मैं अपने निर्भरता रजिस्टर करने के लिए Unity उपयोग कर रहा हूँ:

public static void RegisterTypes(IUnityContainer container) 
    { 
     container.RegisterType<HttpContextBase>(
      new InjectionFactory(_ => new HttpContextWrapper(HttpContext.Current))); 
     container.RegisterType<IOwinContext>(new InjectionFactory(c => c.Resolve<HttpContextBase>().GetOwinContext())); 
     container.RegisterType<IAuthenticationManager>(
      new InjectionFactory(c => c.Resolve<IOwinContext>().Authentication)); 
     container.RegisterType<ILoginHandler, LoginHandler>(); 
     // Further registrations here... 
    } 

हालांकि, मैं अपने एकता पंजीकरण परीक्षण करना चाहते हैं, और यह faking के बिना (क) HttpContext.Current मुश्किल साबित कर दिया है (काफी कठिन) और (बी) GetOwinContext() - जैसा कि आपने पाया है, सीधे करना असंभव है।

मुझे फिल हैक के HttpSimulator के रूप में एक समाधान मिला है और एचटीपी कॉनटेक्स्ट के कुछ हेरफेर को मूल Owin environment बनाने के लिए मिला है। अब तक मुझे पता चला है कि एक डमी ओविन वैरिएबल सेट करना GetOwinContext() काम करने के लिए पर्याप्त है, लेकिन वाईएमएमवी।

public static class HttpSimulatorExtensions 
{ 
    public static void SimulateRequestAndOwinContext(this HttpSimulator simulator) 
    { 
     simulator.SimulateRequest(); 
     Dictionary<string, object> owinEnvironment = new Dictionary<string, object>() 
      { 
       {"owin.RequestBody", null} 
      }; 
     HttpContext.Current.Items.Add("owin.Environment", owinEnvironment); 
    }   
} 

[TestClass] 
public class UnityConfigTests 
{ 
    [TestMethod] 
    public void RegisterTypes_RegistersAllDependenciesOfHomeController() 
    { 
     IUnityContainer container = UnityConfig.GetConfiguredContainer(); 
     HomeController controller; 

     using (HttpSimulator simulator = new HttpSimulator()) 
     { 
      simulator.SimulateRequestAndOwinContext(); 
      controller = container.Resolve<HomeController>(); 
     } 

     Assert.IsNotNull(controller); 
    } 
} 

HttpSimulator अगर आपके SetFakeControllerContext() विधि का काम करता है, लेकिन यह एकीकरण परीक्षण के लिए एक उपयोगी उपकरण की तरह लग रहा overkill हो सकता है।

+0

क्या HttpSimulator() के लिए उपयोग कथन के साथ आप जाने के बहुत महत्वपूर्ण कारण हैं? मान लें कि आप यह चाहते थे कि यह आपके परीक्षण प्रोजेक्ट में अन्य स्थानों पर उपलब्ध हो, आप इसे कैसे हुक करेंगे? (और एक शानदार जवाब के लिए धन्यवाद, मेरी समस्या आश्चर्यजनक ढंग से हल)। – Hanshan

+1

@nulliusinverba HttpSimulator का निपटान HttpContext.Current वापस शून्य करने के लिए ताकि आप अन्य परीक्षणों को दूषित करने का जोखिम नहीं उठा सकें। इसे कहीं और उपलब्ध कराने के बारे में अच्छा सवाल: मैंने SimulateRequestAndOwinContext को निजी से एक विस्तार विधि में बदल दिया है, जो नौकरी करता है और सिंटैक्स को मूल 'सिम्युलेटर .imulateRequest() 'के करीब रखता है। (आप HttpSimulator को भी उपclass कर सकते हैं और SimulateRequest() के आभासी अधिभार को ओवरराइड कर सकते हैं।) – Blisco

4

मेरी ज़रूरतें समान हैं, लेकिन मुझे एहसास हुआ कि मैं अपने खाता नियंत्रक का शुद्ध इकाई परीक्षण नहीं चाहता हूं। इसके बजाय मैं इसे ऐसे माहौल में परीक्षण करना चाहता हूं जो अपने प्राकृतिक आवास (एकीकरण परीक्षण, यदि आप चाहें) के करीब जितना संभव हो। तो मैं आस-पास की वस्तुओं को नकल नहीं करना चाहता, लेकिन वास्तविक लोगों का उपयोग करता हूं, जैसा कि मैं अपने स्वयं के कोड के साथ दूर कर सकता हूं।

HttpContextBaseExtensions.GetOwinContext विधि भी मेरे रास्ते में आई, इसलिए मैं ब्लिस्को के संकेत से बहुत खुश था। अब मेरी समाधान का सबसे महत्वपूर्ण हिस्सा इस तरह दिखता है:

/// <summary> Set up an account controller with just enough context to work through the tests. </summary> 
/// <param name="userManager"> The user manager to be used </param> 
/// <returns>A new account controller</returns> 
private static AccountController SetupAccountController(ApplicationUserManager userManager) 
{ 
    AccountController controller = new AccountController(userManager); 
    Uri url = new Uri("https://localhost/Account/ForgotPassword"); // the real string appears to be irrelevant 
    RouteData routeData = new RouteData(); 

    HttpRequest httpRequest = new HttpRequest("", url.AbsoluteUri, ""); 
    HttpResponse httpResponse = new HttpResponse(null); 
    HttpContext httpContext = new HttpContext(httpRequest, httpResponse); 
    Dictionary<string, object> owinEnvironment = new Dictionary<string, object>() 
    { 
     {"owin.RequestBody", null} 
    }; 
    httpContext.Items.Add("owin.Environment", owinEnvironment); 
    HttpContextWrapper contextWrapper = new HttpContextWrapper(httpContext); 

    ControllerContext controllerContext = new ControllerContext(contextWrapper, routeData, controller); 
    controller.ControllerContext = controllerContext; 
    controller.Url = new UrlHelper(new RequestContext(contextWrapper, routeData)); 
    // We have not found out how to set up this UrlHelper so that we get a real callbackUrl in AccountController.ForgotPassword. 

    return controller; 
} 

मैं अभी तक (विशेष रूप से, मैं UrlHelper ForgotPassword विधि में एक उचित यूआरएल उत्पादन करने के लिए नहीं मिल सका) सब कुछ काम कर पाने के सफल नहीं है, लेकिन मेरी अधिकांश जरूरतें अब ढकी हुई हैं।

+0

+1 अधिक यथार्थवादी परीक्षण के लिए। अन्य दानेदार होने के दौरान समान रूप से मान्य होते हैं लेकिन कभी-कभी आप परीक्षण स्तर पर अवधारणा का प्रमाण चाहते हैं और यह समाधान मेरे द्वारा किए जा रहे कार्यों के समान होता है (हालांकि मेरे कोड आमलेट की तुलना में बेहतर काम है) –

संबंधित मुद्दे