2015-06-05 11 views
7

yöntem parametreleri için sınayın Denetleyicimi doğrulamak için birim testlerini yazarken, bağlantı özelliklerinin doğru şekilde kurulduğundan emin olmak istiyorum. Aşağıdaki yöntem yapısıyla, sadece geçerli alanların bir birim testinden geçmesini nasıl sağlayabilirim?Birim BindAttribute,

public ActionResult AddItem([Bind(Include = "ID, Name, Foo, Bar")] ItemViewModel itemData) 
{ 
    if (ModelState.IsValid) 
    { 
     // Save and redirect 
    } 

    // Set Error Messages 
    // Rebuild object drop downs, etc. 
    itemData.AllowedFooValues = new List<Foo>(); 
    return View(itemData); 
} 

Geniş Açıklama: bizim modellerin birçoğu, ileri geri göndermek istemiyorum izin değerlerin listeleri var, bu yüzden onları yeniden ne zaman (ModelState.IsValid == false). Tüm bu çalışmaları sağlamak için, listenin yeniden oluşturulduğunu, ancak yöntemi çağırmadan önce listeyi temizlemeden, testin geçersiz olduğunu iddia etmek için birim testleri uygulamak istiyoruz.

Modelin doğrulanmasını sağlamak için bu SO answer numaralı yardımcı yöntemini kullanarak yardımcı yöntemini kullanıyoruz ve sonra birim testimiz böyle bir şeydir.

public void MyTest() 
    { 
     MyController controller = new MyController(); 

     ActionResult result = controller.AddItem(); 
     Assert.IsNotNull(result); 
     ViewResult viewResult = result as ViewResult; 
     Assert.IsNotNull(viewResult); 
     ItemViewModel itemData = viewResult.Model as ItemViewModel; 
     Assert.IsNotNull(recipe); 
     // Validate model, will fail due to null name 
     controller.ValidateViewModel<ItemViewModel, MyController>(itemData); 

     // Call controller action 
     result = controller.AddItem(itemData); 
     Assert.IsNotNull(result); 
     viewResult = result as ViewResult; 
     Assert.IsNotNull(viewResult); 
     itemData = viewResult.Model as ItemViewModel; 
     // Ensure list was rebuilt 
     Assert.IsNotNull(itemData.AllowedFooValues); 
    } 

Doğru yönde herhangi bir yardım veya işaretçi büyük beğeni topluyor.

+0

public static void PreBindModel(Controller controller, ViewModelBase viewModel, string operationName) { MethodInfo[] methods = controller.GetType().GetMethods(); foreach (MethodInfo currentMethod in methods) { if (currentMethod.Name.Equals(operationName)) { bool foundParamAttribute = false; foreach (ParameterInfo paramToAction in currentMethod.GetParameters()) { object[] attributes = paramToAction.GetCustomAttributes(true); foreach (object currentAttribute in attributes) { BindAttribute bindAttribute = currentAttribute as BindAttribute; if (bindAttribute == null) continue; PropertyInfo[] allProperties = viewModel.GetType().GetProperties(); IEnumerable<PropertyInfo> propertiesToReset = allProperties.Where(x => bindAttribute.IsPropertyAllowed(x.Name) == false); foreach (PropertyInfo propertyToReset in propertiesToReset) { propertyToReset.SetValue(viewModel, null); } foundParamAttribute = true; } } if (foundParamAttribute) return; } } } 

Genel olarak bu yüzden şimdi benim testleri aşağıdaki gibi görünür, bir çok temiz ve kolay çözüm oldu Ne aradığınızı net değil. Bağlama Özniteliğinin kullanıldığını ve denetleyicinizdeki (ID, Foo ...) doğru değerler ile ayarlandığını algılamanın bir yolunu mu arıyorsunuz? Veya MVC çalışma zamanının özniteliği doğru şekilde kullandığını test etmenin bir yolunu mu arıyorsunuz? Veya MVC çalışma zamanının davranışını yeniden oluşturmak için niteliği test modelinize manuel olarak uygulamanın bir yolu, böylece yöntemlerinizi test edebilirsiniz? Ya da tamamen başka bir şey? – forsvarir

+0

Bağlama özniteliğinin uygulandığını sınamanın bir yolunu arıyorum, böylece bir modelin ciltleme için bildirilmemiş alanlar varsa, değerler, sınamalardan geçmeyecek şekilde sınamadan denetleyiciye aktarılmazlar. Bir yazı üzerindeki kontrolöre görünümden. Son hedef, tüm alanların denetleyiciye bir gönderi üzerinde güncelleştirilen yalnızca ilişkili değerler ile doğru bir şekilde bağlı olduğundan emin olmak ve "kötü" bir model (bazı sunucu doğrulama durumları için gerçekleşir) yayınlayabilmek ve Bir test tarafından uygulanan if (ModelState.IsValid) mantığı. –

+1

Bu işe yarıyor harika. Çözümünüzün sorgunuzda düzenlenmesi genellikle iyi bir fikir değildir, çünkü bir sorudan ziyade etkin bir cevaptır. Sorunuzdan düzenlemek ve bunun yerine soruya bir cevap olarak göndermek isteyebilirsiniz. Soru-Cevap bölümünü bölümlendirmeye yardımcı olur ve bonus olarak sizin için tuhaf bir oylama ile sonuçlanabilir. Kişilerin, gönderiye baktıkları sırada sorununun yanında kalmasını istiyorsanız, o zaman kabul edilen cevabı yazılarınıza da aktarabilirsiniz (diğer insanların görüşlerinin farklı olmasına rağmen bir sorunum yok) – forsvarir

cevap

2

Söylediklerinizi yanlış yorumluyor olabilirim, ancak MVC bağlayıcılığını simüle etmek için denetleyicinize iletilmeden önce sınamanızda oluşturduğunuz bir modelin filtrelenmesini sağlamak için bir şey olmasını istediğiniz gibi geliyor. Yanlışlıkla denetleyicinize hiçbir zaman gerçekten de bu çerçeve tarafından doldurulmayacak olan sınama altında bilgi içeren bir sınama yazmanızı önler.

Bu düşünceyle, yalnızca Include üye grubuyla Bind öznitelikleriyle gerçekten ilgilendiğinizi varsaydım. Bu durumda böyle bir şey kullanabilirsiniz:

public static void PreBindModel<TViewModel, TController>(this TController controller, 
                 TViewModel viewModel, 
                 string operationName) { 
    foreach (var paramToAction in typeof(TController).GetMethod(operationName).GetParameters()) { 
     foreach (var bindAttribute in paramToAction.CustomAttributes.Where(x => x.AttributeType == typeof(BindAttribute))) { 
      string properties; 
      try { 
       properties = bindAttribute.NamedArguments.Where(x => x.MemberName == "Include").First().TypedValue.Value.ToString(); 
      } 
      catch (InvalidOperationException) { 
       continue; 
      } 
      var propertyNames = properties.Split(','); 

      var propertiesToReset = typeof(TViewModel).GetProperties().Where(x => propertyNames.Contains(x.Name) == false); 

      foreach (var propertyToReset in propertiesToReset) { 
       propertyToReset.SetValue(viewModel, null); 
      } 
     } 
    } 
} 

hangisi seni böyle kontrolör eylemi çağırmak önce, bu birim testinden elde aranmak haliyle: Esasen

controllerToTest.PreBindModel(model, "SomeMethod"); 
var result = controllerToTest.SomeMethod(model); 

ne yaptığını, Bağlama özniteliklerini aramak için belirli bir denetleyici yöntemine geçirilen her bir parametreden yinelenir. Bir bağlama özniteliği bulursa, Include listesini alır, sonra viewModel'un içerme listesinde belirtilmeyen tüm özelliklerini sıfırlar (temelde açmadan).

Yukarıdaki kodda biraz değişiklik yapılması gerekebilir, çok fazla MVC çalışması yapmıyorum, bu nedenle özellik ve modellerin kullanımı hakkında bazı varsayımlarda bulundum.

filtreleme yapmak BindAttribute kendisi kullanır Yukarıdaki kod geliştirilmiş sürümü,: Forsvarir tarafından sağlanan cevap dayanarak

public static void PreBindModel<TViewModel, TController>(this TController controller, TViewModel viewModel, string operationName) { 
    foreach (var paramToAction in typeof(TController).GetMethod(operationName).GetParameters()) { 
     foreach (BindAttribute bindAttribute in paramToAction.GetCustomAttributes(true)) {//.Where(x => x.AttributeType == typeof(BindAttribute))) { 
      var propertiesToReset = typeof(TViewModel).GetProperties().Where(x => bindAttribute.IsPropertyAllowed(x.Name) == false); 

      foreach (var propertyToReset in propertiesToReset) { 
       propertyToReset.SetValue(viewModel, null); 
      } 
     } 
    } 
} 
+0

Bu, aradığım şeye yakın. MVC'de, taklit etmek yerine gerçek bağlayıcı kodunu tetiklememi sağlayacak test için yerleşik bir özellik olduğunu umuyordum. En kötü durum senaryosu gerekirse bu yoldan gidebilirim. –

+0

@MartinNoreke BindAttribute'ü filtrelemeyi yapmak için kullanmak için biraz daha geliştirdim, ancak yine de modelin doldurulduğunu varsayar ve orada olmaması gereken değerleri çıkarır. Çalışma zamanı ile bundan daha yakınlaşmayı düşünüyorum, aslında bağlayıcı mantığı bir denetleyici bağlamı ve değer sağlayıcısıyla başlatmaya başlamanız gerektiğini görebilirsiniz. Eğer peşinde olanı alamıyorsanız, o zaman MVC kaynağının etrafında bir kazı yapmak isteyebilirsiniz: https://aspnetwebstack.codeplex.com/ – forsvarir

+0

Bir veya iki gün içinde buna bir kazmaya başlayacağım. Başka bir iş üzerinde çektim (bu asla olmaz :), ama bunun beni doğru yönde yönlendirdiğini düşünüyorum. –

1

, benim son uygulama olarak bu geldi. Her kullanımda yazmayı azaltmak için jenerikleri kaldırdım ve bunu testlerimin temel bir sınıfına koydum. Aynı isimde, ancak GetMethod yerine tüm yöntemlerin döngüsüyle çözülen farklı parametreler (ex: Get vs. Post) ile birden fazla yöntem için ekstra çalışma yapmak zorunda kaldım.

Sadece başvuru için, benim ValidateViewModel yöntemidir
[TestMethod] 
public void MyTest() 
{ 
    MyController controller = new MyController(); 

    ActionResult result = controller.MyAddMethod(); 
    Assert.IsNotNull(result); 
    ViewResult viewResult = result as ViewResult; 
    Assert.IsNotNull(viewResult); 
    MyDataType myDataObject = viewResult.Model as MyDataType; 
    Assert.IsNotNull(myDataObject); 
    ValidateViewModel(myController, myDataObject); 
    PreBindModel(controller, myDataObject, "MyAddMethod"); 
    Assert.IsNull(myDataObject.FieldThatShouldBeReset); 
    result = controller.MyAddMethod(myDataObject); 
    Assert.IsNotNull(result); 
    viewResult = result as ViewResult; 
    Assert.IsNotNull(viewResult); 
    myDataObject = viewResult.Model as MyDataType; 
    Assert.IsNotNull(myDataObject.FieldThatShouldBeReset); 
} 

: Bu biraz

public static void ValidateViewModel(BaseAuthorizedController controller, ViewModelBase viewModelToValidate) 
    { 
     var validationContext = new ValidationContext(viewModelToValidate, null, null); 
     var validationResults = new List<ValidationResult>(); 
     Validator.TryValidateObject(viewModelToValidate, validationContext, validationResults, true); 
     foreach (var validationResult in validationResults) 
     { 
      controller.ModelState.AddModelError(validationResult.MemberNames.FirstOrDefault() ?? string.Empty, validationResult.ErrorMessage); 
     } 
    }