MVC 유효성 검사에 대한 단위 테스트
MVC 2 Preview 1에서 DataAnnotation 유효성 검사를 사용할 때 엔터티 유효성을 검사 할 때 컨트롤러 작업이 ModelState에 올바른 오류를 표시하는지 어떻게 테스트 할 수 있습니까?
설명 할 몇 가지 코드입니다. 첫째, 조치 :
[HttpPost]
public ActionResult Index(BlogPost b)
{
if(ModelState.IsValid)
{
_blogService.Insert(b);
return(View("Success", b));
}
return View(b);
}
그리고 여기에 통과해야한다고 생각하는 실패한 단위 테스트가 있습니다 (MbUnit 및 Moq 사용).
[Test]
public void When_processing_invalid_post_HomeControllerModelState_should_have_at_least_one_error()
{
// arrange
var mockRepository = new Mock<IBlogPostSVC>();
var homeController = new HomeController(mockRepository.Object);
// act
var p = new BlogPost { Title = "test" }; // date and content should be required
homeController.Index(p);
// assert
Assert.IsTrue(!homeController.ModelState.IsValid);
}
나는이 질문에 추가로 생각 해야 내가 검증을 테스트 할, 나는이 방법으로 테스트를해야 하는가?
를 전달하는 대신 BlogPost
actions 매개 변수를 FormCollection
. 그런 다음 BlogPost
자신을 만들고을 호출 할 수 있습니다 UpdateModel(model, formCollection.ToValueProvider());
.
그러면 .NET Framework의 모든 필드에 대한 유효성 검사가 트리거됩니다 FormCollection
.
[HttpPost]
public ActionResult Index(FormCollection form)
{
var b = new BlogPost();
TryUpdateModel(model, form.ToValueProvider());
if (ModelState.IsValid)
{
_blogService.Insert(b);
return (View("Success", b));
}
return View(b);
}
테스트에서 비워 두려는보기 양식의 모든 필드에 대해 null 값을 추가하는지 확인하십시오.
이렇게하면 코드 몇 줄을 추가로 사용하는 대신 단위 테스트가 런타임에 코드가 더 가깝게 호출되는 방식과 비슷 해져 더 가치가 있다는 것을 알았습니다. 또한 누군가가 int 속성에 바인딩 된 컨트롤에 "abc"를 입력하면 어떤 일이 발생하는지 테스트 할 수 있습니다.
오래된 게시물을 괴롭히는 것을 싫어하지만 내 생각을 추가 할 것이라고 생각했습니다 (방금이 문제가 발생하여 답변을 찾는 동안이 게시물을 살펴 봤기 때문에).
- 컨트롤러 테스트에서 유효성 검사를 테스트하지 마십시오. MVC의 유효성 검사를 신뢰하거나 직접 작성하십시오 (즉, 다른 사람의 코드를 테스트하지 말고 코드를 테스트하십시오).
- 유효성 검사가 예상 한대로 작동하는지 테스트하려면 모델 테스트에서 테스트합니다 (더 복잡한 몇 가지 정규식 유효성 검사를 위해이 작업을 수행합니다).
여기서 테스트하고 싶은 것은 컨트롤러가 유효성 검사가 실패 할 때 예상 한대로 수행한다는 것입니다. 그것이 당신의 코드이고 당신의 기대입니다. 테스트하려는 것이 전부라는 것을 깨닫고 나면 쉽게 테스트 할 수 있습니다.
[test]
public void TestInvalidPostBehavior()
{
// arrange
var mockRepository = new Mock<IBlogPostSVC>();
var homeController = new HomeController(mockRepository.Object);
var p = new BlogPost();
homeController.ViewData.ModelState.AddModelError("Key", "ErrorMessage"); // Values of these two strings don't matter.
// What I'm doing is setting up the situation: my controller is receiving an invalid model.
// act
var result = (ViewResult) homeController.Index(p);
// assert
result.ForView("Index")
Assert.That(result.ViewData.Model, Is.EqualTo(p));
}
나는 같은 문제를 겪고 있었고 Pauls의 답변과 의견을 읽은 후 뷰 모델을 수동으로 검증하는 방법을 찾았습니다.
DataAnnotations를 사용하는 ViewModel을 수동으로 검증하는 방법을 설명하는 이 튜토리얼 을 찾았습니다 . 키 코드 스 니펫은 게시물 끝 부분에 있습니다.
코드를 약간 수정했습니다. 튜토리얼에서 TryValidateObject의 4 번째 매개 변수가 생략되었습니다 (validateAllProperties). 모든 주석을 Validate로 가져 오려면이 값을 true로 설정해야합니다.
추가로 ViewModel 유효성 검사를 간단하게 테스트하기 위해 코드를 일반 메서드로 리팩터링했습니다.
public static void ValidateViewModel<TViewModel, TController>(this TController controller, TViewModel viewModelToValidate)
where TController : ApiController
{
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);
}
}
지금까지 이것은 우리에게 정말 잘 작동했습니다.
테스트에서 homeController.Index 메서드를 호출 할 때 유효성 검사를 실행하는 MVC 프레임 워크를 사용하지 않으므로 ModelState.IsValid가 항상 true가됩니다. 우리 코드에서는 앰비언트 유효성 검사를 사용하지 않고 컨트롤러에서 직접 도우미 Validate 메서드를 호출합니다. DataAnnotations에 대한 경험이 많지 않은 경우 (NHibernate.Validators 사용) 다른 사람이 컨트롤러 내에서 Validate를 호출하는 방법에 대한 지침을 제공 할 수 있습니다.
나는 오늘 이것을 조사하고 있었고 Roberto Hernández (MVP) 의이 블로그 게시물이 단위 테스트 중에 컨트롤러 작업에 대한 유효성 검사기를 실행하는 최상의 솔루션을 제공하는 것으로 보이는 것을 발견 했습니다 . 엔티티를 검증 할 때 ModelState에 올바른 오류가 발생합니다.
Model.IsValid 값을 업데이트 할 수 있도록 테스트 케이스에서 ModelBinders를 사용하고 있습니다.
var form = new FormCollection();
form.Add("Name", "0123456789012345678901234567890123456789");
var model = MvcModelBinder.BindModel<AddItemModel>(controller, form);
ViewResult result = (ViewResult)controller.Add(model);
내 MvcModelBinder.BindModel 메서드를 다음과 같이 사용합니다 (기본적으로 MVC 프레임 워크에서 내부적으로 사용되는 동일한 코드).
public static TModel BindModel<TModel>(Controller controller, IValueProvider valueProvider) where TModel : class
{
IModelBinder binder = ModelBinders.Binders.GetBinder(typeof(TModel));
ModelBindingContext bindingContext = new ModelBindingContext()
{
FallbackToEmptyPrefix = true,
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(TModel)),
ModelName = "NotUsedButNotNull",
ModelState = controller.ModelState,
PropertyFilter = (name => { return true; }),
ValueProvider = valueProvider
};
return (TModel)binder.BindModel(controller.ControllerContext, bindingContext);
}
이것은 DataAnnotations를 버리기 때문에 귀하의 질문에 정확히 대답하지는 않지만 다른 사람들이 컨트롤러에 대한 테스트를 작성하는 데 도움이 될 수 있기 때문에 추가하겠습니다.
System.ComponentModel.DataAnnotations에서 제공하는 유효성 검사를 사용하지 않지만 해당 AddModelError
메서드 및 기타 유효성 검사 메커니즘을 사용하여 ViewData.ModelState 개체를 계속 사용하는 옵션이 있습니다. 예 :
public ActionResult Create(CompetitionEntry competitionEntry)
{
if (competitionEntry.Email == null)
ViewData.ModelState.AddModelError("CompetitionEntry.Email", "Please enter your e-mail");
if (ModelState.IsValid)
{
// insert code to save data here...
// ...
return Redirect("/");
}
else
{
// return with errors
var viewModel = new CompetitionEntryViewModel();
// insert code to populate viewmodel here ...
// ...
return View(viewModel);
}
}
이렇게하면 Html.ValidationMessageFor()
.NET Framework를 사용하지 않고도 MVC가 생성하는 항목 을 활용할 수 있습니다 DataAnnotations
. 사용하는 키 AddModelError
가 뷰가 유효성 검사 메시지에 대해 예상하는 것과 일치하는지 확인해야합니다.
The controller then becomes testable because the validation is happening explicitly, rather than being done automagically by the MVC framework.
I agree that ARM has the best answer: test the behaviour of your controller, not the built-in validation.
However, you can also unit test that your Model/ViewModel has the correct validation attributes defined. Let's say your ViewModel looks like this:
public class PersonViewModel
{
[Required]
public string FirstName { get; set; }
}
This unit test will test for the existence of the [Required]
attribute:
[TestMethod]
public void FirstName_should_be_required()
{
var propertyInfo = typeof(PersonViewModel).GetProperty("FirstName");
var attribute = propertyInfo.GetCustomAttributes(typeof(RequiredAttribute), false)
.FirstOrDefault();
Assert.IsNotNull(attribute);
}
In contrast to ARM, I don't have a problem with grave digging. So here is my suggestion. It builds on the answer of Giles Smith and works for ASP.NET MVC4 (I know the question is about MVC 2, but Google doesn't discriminate when looking for answers and I cannot test on MVC2.) Instead of putting the validation code in a generic static method, I put it in a test controller. The controller has everything needed for validation. So, the test controller looks like this:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Wbe.Mvc;
protected class TestController : Controller
{
public void TestValidateModel(object Model)
{
ValidationContext validationContext = new ValidationContext(Model, null, null);
List<ValidationResult> validationResults = new List<ValidationResult>();
Validator.TryValidateObject(Model, validationContext, validationResults, true);
foreach (ValidationResult validationResult in validationResults)
{
this.ModelState.AddModelError(String.Join(", ", validationResult.MemberNames), validationResult.ErrorMessage);
}
}
}
Of course the class does not need to be a protected innerclass, that is the way I use it now but I probably am going to reuse that class. If somewhere there is a model MyModel that is decorated with nice data annotation attributes, then the test looks something like this:
[TestMethod()]
public void ValidationTest()
{
MyModel item = new MyModel();
item.Description = "This is a unit test";
item.LocationId = 1;
TestController testController = new TestController();
testController.TestValidateModel(item);
Assert.IsTrue(testController.ModelState.IsValid, "A valid model is recognized.");
}
The advantage of this setup is that I can reuse the test controller for tests of all my models and may be able to extend it to mock a bit more about the controller or use the protected methods that a controller has.
Hope it helps.
If you care about validation but you don't care about how it is implemented, if you only care about validation of your action method at the highest level of abstraction, no matter whether it is implemented as using DataAnnotations, ModelBinders or even ActionFilterAttributes, then you could use Xania.AspNet.Simulator nuget package as follows:
install-package Xania.AspNet.Simulator
--
var action = new BlogController()
.Action(c => c.Index(new BlogPost()), "POST");
var modelState = action.ValidateRequest();
modelState.IsValid.Should().BeFalse();
Based on @giles-smith 's answer and comments, for Web API:
public static void ValidateViewModel<TViewModel, TController>(this TController controller, TViewModel viewModelToValidate)
where TController : ApiController
{
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);
}
}
See on answer edit above...
@giles-smith's answer is my preferred approach but the implementation can be simplified:
public static void ValidateViewModel(this Controller controller, object 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);
}
}
참고URL : https://stackoverflow.com/questions/1269713/unit-tests-on-mvc-validation
'Nice programing' 카테고리의 다른 글
내 MVC 애플리케이션에 대한 서비스 계층을 생성합니까? (0) | 2020.10.18 |
---|---|
uitabbarcontroller를 숨기는 방법 (0) | 2020.10.17 |
UICollectionView는 상단 여백 추가 (0) | 2020.10.17 |
스위치에서 케이스 문으로 배열 사용 (0) | 2020.10.17 |
PHP에서 날짜 시간에 분 추가 (0) | 2020.10.17 |