문제

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

아마 이 질문 외에도 ~해야 한다 유효성 검사를 테스트하고 있는데 이런 방식으로 테스트해야 합니까?

도움이 되었습니까?

해결책

a BlogPost 작업 매개 변수를 다음과 같이 선언 할 수도 있습니다 FormCollection. 그러면 당신은 그것을 만들 수 있습니다 BlogPost 자신과 전화 UpdateModel(model, formCollection.ToValueProvider());.

이것은 모든 필드에 대한 검증을 트리거합니다. 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);
    }

테스트가 비워두고 싶은 뷰 형태의 모든 필드에 대해 널 값을 추가해야합니다.

나는 몇 줄의 추가 코드 라인을 희생시키면서 이런 식으로이를 수행하면 단위 테스트가 런타임에 코드가 호출되는 방식과 비슷하게 만들어 졌다는 것을 알았습니다. 또한 누군가가 INT 속성에 묶인 컨트롤에서 "ABC"에 들어갈 때 어떤 일이 발생하는지 테스트 할 수 있습니다.

다른 팁

Necro는 오래된 게시물을 싫어하지만, 나는 내 자신의 생각을 추가 할 것이라고 생각했습니다 (방금이 문제가 있었고 답을 찾는 동안이 게시물을 가로 질러 달렸 기 때문에).

  1. 컨트롤러 테스트에서 검증을 테스트하지 마십시오. MVC의 검증을 신뢰하거나 자신의 글을 쓰십시오 (즉, 다른 사람의 코드를 테스트하지 말고 코드를 테스트하십시오).
  2. 검증을 테스트하려면 예상되는 일을 수행하는 경우 모델 테스트에서 테스트합니다 (더 복잡한 Regex 유효성 검사에 대해서는이 작업을 수행합니다).

여기서 실제로 테스트하고 싶은 것은 컨트롤러가 검증이 실패 할 때 기대하는 일을한다는 것입니다. 그것이 당신의 코드와 기대입니다. 테스트 테스트는 테스트하고 싶은 전부라는 것을 깨달았을 때 쉽습니다.

[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 답변과 의견을 읽은 후에는보기 모델을 수동으로 검증하는 방법을 찾았습니다.

나는 찾았다 이 튜토리얼 데이터 알노화를 사용하는 뷰 모델을 수동으로 검증하는 방법을 설명합니다. 키 코드 스 니펫은 게시물의 끝입니다.

코드를 약간 수정했습니다. 튜토리얼에서 TryvalidateObject의 4 번째 매개 변수는 생략되었습니다 (ValidAteAleLproperties). 모든 주석이 검증되도록하려면 이렇게 설정되어야합니다.

추가로 코드를 일반적인 방법으로 리팩토링하여 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는 항상 사실입니다. 우리의 코드에서 우리는 주변 유효성 검사를 사용하지 않고 컨트롤러에서 직접 헬퍼 Validate 메소드를 호출합니다. 데이터 알 냄비에 대한 경험이 많지 않았습니다 (우리는 nhibernate.validators를 사용합니다) 다른 사람이 컨트롤러 내에서 Validate를 호출하는 방법을 제공 할 수 있습니다.

나는 오늘 이것을 연구하고 있었고 나는 발견했다 이 블로그 게시물 : Roberto Hernández (MVP)는 단위 테스트 중에 컨트롤러 조치를위한 유효성 검사기를 발사하기위한 최상의 솔루션을 제공하는 것으로 보입니다. 엔터티를 검증 할 때 ModelState에 올바른 오류가 발생합니다.

테스트 케이스에서 ModelBinders를 사용하여 Model.isValid 값을 업데이트 할 수 있습니다.

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에서 제공하는 유효성 검사를 사용하지 않고 ViewData.ModelState 개체를 계속 사용할 수 있는 옵션이 있습니다. AddModelError 방법 및 기타 유효성 검사 메커니즘.예:

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() MVC가 생성하는 것들은 DataAnnotations.당신은 당신이 사용하는 키를 확인해야합니다 AddModelError 보기가 유효성 검사 메시지에 대해 기대하는 것과 일치합니다.

그런 다음 MVC 프레임워크에 의해 자동으로 수행되는 대신 유효성 검사가 명시적으로 수행되므로 컨트롤러를 테스트할 수 있게 됩니다.

ARM은 최상의 답변이 있다는 데 동의합니다. 내장 검증이 아니라 컨트롤러의 동작을 테스트합니다.

그러나 모델/ViewModel에 올바른 유효성 검사 속성이 정의되어 있음을 단위 테스트 할 수도 있습니다. ViewModel이 다음과 같이 보입니다.

public class PersonViewModel
{
    [Required]
    public string FirstName { get; set; }
}

이 단위 테스트는 [Required] 기인하다:

[TestMethod]
public void FirstName_should_be_required()
{
    var propertyInfo = typeof(PersonViewModel).GetProperty("FirstName");

    var attribute = propertyInfo.GetCustomAttributes(typeof(RequiredAttribute), false)
                                .FirstOrDefault();

    Assert.IsNotNull(attribute);
}

팔과 달리, 나는 무덤 파기에 문제가 없습니다. 그래서 여기 내 제안이 있습니다. Giles Smith의 답변을 바탕으로 ASP.NET MVC4에서 일하고 있습니다 (질문은 MVC 2에 관한 것이지만 Google은 답변을 찾을 때 차별하지 않으며 MVC2에서 테스트 할 수 없습니다.) 일반적인 정적 방법으로 테스트 컨트롤러에 넣습니다. 컨트롤러에는 검증에 필요한 모든 것이 있습니다. 따라서 테스트 컨트롤러는 다음과 같습니다.

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

물론 수업은 보호 된 내부 클래스 일 필요는 없습니다. 그것은 내가 지금 사용하는 방식이지만 아마도 그 클래스를 재사용 할 것입니다. 어딘가에 멋진 데이터 주석 속성으로 장식 된 모델 mymodel이 있다면 테스트는 다음과 같이 보입니다.

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

이 설정의 장점은 모든 모델의 테스트를 위해 테스트 컨트롤러를 재사용 할 수 있고 컨트롤러에 대해 조금 더 조롱하거나 컨트롤러가 가지고있는 보호 된 방법을 사용할 수 있다는 것입니다.

도움이되기를 바랍니다.

유효성 검사에 관심이 있지만 구현 방법에 관심이없는 경우, 데이터 오노 테이션, 모델 바인더 또는 ActionFilterattributes를 사용하여 구현 되든 상관없이 최고 수준의 추상화에서 동작 방법의 검증에만 관심이있는 경우. 다음과 같이 xania.aspnet.simulator nuget 패키지를 사용할 수 있습니다.

install-package Xania.AspNet.Simulator

--

var action = new BlogController()
    .Action(c => c.Index(new BlogPost()), "POST");
var modelState = action.ValidateRequest();

modelState.IsValid.Should().BeFalse();

웹 API에 대한 @Giles-Smith의 답변 및 의견을 기반으로합니다.

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

위의 답변 편집을 참조하십시오 ...

@Giles-Smith의 답변은 내가 선호하는 접근 방식이지만 구현을 단순화 할 수 있습니다.

    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);
        }
    }
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top