2013-10-27 26 views
26

I (öğretici Create an ASP.NET MVC 5 App with Facebook and Google OAuth2 and OpenID Sign-on (C#) gösterildiği gibi) yeni bir özellik ekleyerek ApplicationUser sınıfını uzatıyorumBirim Test ASP.NET MVC5 App

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

Şimdi doğrulamak için bir birim test oluşturmak istiyorum benim AccountController BirthDate'i doğru şekilde kaydediyor.

Ben controller.Register yöntemi referans amaçlı Ben burada dahil ediyorum MVC5 tarafından oluşturulan Demirbaş kodu ancak 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); 
} 

adlı bir bellek içi kullanıcı mağaza oluşturduk.

// 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); 
} 

Kayıt'ı aradığımda, sorunun nerede oluşacağını SignInAsync çağırır. düşük katmanda

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); 
} 

, demirbaş kod çerez

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

problm kök meydana geldiği Bu içerir. GetOwinContext'e yapılan bu çağrı, taklit edemediğim ve bir saplama ile değiştiremediğim bir uzantı yöntemidir (tabiki kod değiştirmedikçe). Ben bu testi çalıştırdığınızda

ASP.NET MVC ekibi kodu test edilebilir hale getirmek için çok çalıştık bir istisna önceki sürümlerde

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 

olsun. Şimdi bir AccountController'ı test etmenin kolay olmayacağı yüzeyde görünüyor. Bazı seçimlerim var. o bir uzantısı yöntemini çağırın ve o seviyede bu sorunla

  • Kurulumu test amaçlı Owin boru hattı başa kalmaması

    Ben

    1. kazan plaka kodunu değiştirin edebilirsiniz

    2. AuthN/AuthZ altyapısını gerektiren bir sınama kodu yazmaktan kaçının (makul bir seçenek değil)

    Hangi yolun daha iyi olduğundan emin değilim. Ya bunu çözebiliriz. Benim sorum şu ki en iyi strateji hangisidir.

    Not: Yazmadığım kodu sınamanıza gerek duymadığımı biliyorum. UserManager altyapısı MVC5'i sağladı ancak böyle bir uygulama altyapısı var ama eğer benim Uygulama Yapımcı modifikasyonlarımı doğrulayan testler yazmak veya kullanıcı rollerine bağlı davranışları doğrulayan bir kod yazmak istiyorsanız UserManager'ı kullanarak test etmeliyim.

  • +0

    hi hakkında söz öğretici işaret lütfen edebilirsiniz. Sorunuzu, Kayıt yönteminin uygulamanızla güncelleyebiliyorsanız, yardımcı olacaktır. – Spock

    +0

    HttpContext, birim testleriniz referans alındığında boş bırakılacaktır. Kullandığınız özelliklerin değerlerini döndürmek için sahte bir HttpContext nesnesi enjekte etmeniz gerekebilir. –

    +0

    Sahte bir kurulum yaptım - yukarıdaki örnek kodda değildim ama şimdi ekledim. –

    cevap

    26

    Kendi sorumu cevaplıyorum, bu yüzden bunun iyi bir yanıt olduğunu düşünüyorsanız, topluluktan bir anlam çıkarabilirim.

    Aşama 1: bir destek alanını kullanarak AuthenticationManager için bir özellik belirleyici sağlamak için oluşturulan AccountController değiştirin.Aşama 2

    // 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; } 
    } 
    

    :

    [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); 
    } 
    

    Şimdi test başarılı Microsoft.OWin.IAuthenticationManager arayüzü için bir sahte ekleme Birim test değiştirin.

    İyi fikir? Kötü bir fikir?

    +4

    Muhtemelen IAuthenticationManager'ı özel bir salt okunur alan haline getirebilirim ve kurucu aracılığıyla ayarlayabilirim. –

    +0

    Sadece çok küçük bir değişiklik, ancak AuthenticationManager'ın get özelliği ?? kullanılarak yazılabilir, bu nedenle boş çekmeyi gereksiz kılar. – viniciushana

    +1

    Mock nedir? Nasıl kullanabilirim? –

    3

    ben sizinkine benzer bir çözüm kullandım - Bir IAuthenticationManager alay - ancak giriş kodu yapıcı enjeksiyon yoluyla IAuthenticationManager götüren bir LoginManager sınıfındadır. Benim bağımlılıkları kayıt Unity kullanıyorum

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

    : (Bununla birlikte

    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... 
        } 
    

    , benim Birlik kayıtlarını test etmek istiyorum ve bu, (a) HttpContext.Current taklit etmeden zor olduğunu kanıtlamıştır Yeterince sert) ve (b) GetOwinContext() - bulduğunuz gibi, doğrudan yapmak imkansız.

    Phil Haack'ın HttpSimulator biçiminde bir çözüm buldum ve temel bir Owin environment oluşturmak için HttpContext'in bazı işlemleri. Şimdiye kadar tek bir kukla Owin değişkeninin GetOwinContext() çalışmasını yapmak için yeterli olduğunu, ancak YMMV olduğunu buldum.

    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 sizin SetFakeControllerContext() yöntemi işi yapar, ancak entegrasyon testi için yararlı bir araç gibi görünüyor eğer overkill olabilir.

    +0

    HttpSimulator() için bir kullanım deyimiyle gittiğiniz çok önemli nedenler var mı? Bunu test projenizdeki diğer yerlerde de görmek istediğinizi söyleyin, bunu nasıl bağlarsınız? (Ve parlak bir cevap için teşekkürler, sorunumu harika bir şekilde çözdü). – Hanshan

    +1

    @nulliusinverba HttpSimulator ürününü HttpContext.Current ile sıfırlayarak yeniden sınanırsınız, böylece diğer sınamaları kirletme riskiniz yoktur. Bunu başka bir yerde kullanılabilir hale getirme hakkında iyi bir soru: SimulateRequestAndOwinContext'i özel bir uzantı yöntemine dönüştürdüm. Bu, işi yapan ve aynı zamanda sözdizimini orijinal 'simulator.SimulateRequest()' 'e yaklaştırıyor. (Ayrıca daha gerçekçi bir test için HttpSimulator alt sınıfını ve SimulateRequest() sanal aşırı yüklenmesini geçersiz kılabilirsiniz.) – Blisco

    4

    Benim ihtiyaçları benzer, ancak benim AccountController saf birim testi istemediğini fark etti. Bunun yerine, doğal ortamına mümkün olduğunca yakın bir ortamda test etmek istiyorum (eğer isterseniz, entegrasyon testi). Bu yüzden etraftaki nesnelere alay etmek istemiyorum ama gerçek kodları kullanarak, kendi kodumdan azımla kaçabiliyorum.

    HttpContextBaseExtensions.GetOwinContext yöntemi

    de yoluma çıktı, bu yüzden Blisco en ipucu ile çok mutlu oldu. Şimdi benim çözümün en önemli parçası şuna benzer:

    /// <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; 
    } 
    

    henüz (özellikle ben UrlHelper ForgotPassword yönteminde doğru bir URL üretmek için alamadım) her şeyi çalışma almak başardı, ancak henüz ihtiyaçlarımın çoğu şimdi karşılanmaktadır.

    +0

    +1. Diğerleri, granüler olmakla aynı derecede geçerlidir, ancak bazen test seviyesinde bir konsept kanıtı istersiniz ve bu çözüm, çalıştığım şeye benziyor (kod omurgamdan daha iyi bir iş olsa da). –