Unit tests on MVC validation

前端 未结 12 1207
离开以前
离开以前 2020-12-04 06:31

How can I test that my controller action is putting the correct errors in the ModelState when validating an entity, when I\'m using DataAnnotation validation in MVC 2 Previe

相关标签:
12条回答
  • 2020-12-04 06:47

    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.

    0 讨论(0)
  • 2020-12-04 06:48

    @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);
            }
        }
    
    0 讨论(0)
  • 2020-12-04 06:49

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

    0 讨论(0)
  • 2020-12-04 06:54

    I was researching this today and I found this blog post by Roberto Hernández (MVP) that seems to provide the best solution to fire the validators for a controller action during unit testing. This will put the correct errors in the ModelState when validating an entity.

    0 讨论(0)
  • 2020-12-04 06:56

    When you call the homeController.Index method in your test, you aren't using any of the MVC framework that fires off the validation so ModelState.IsValid will always be true. In our code we call a helper Validate method directly in the controller rather than using ambient validation. I haven't had much experience with the DataAnnotations (We use NHibernate.Validators) maybe someone else can offer guidance how to call Validate from within your controller.

    0 讨论(0)
  • 2020-12-04 07:00

    Hate to necro a old post, but I thought I'd add my own thoughts (since I just had this problem and ran across this post while seeking the answer).

    1. Don't test validation in your controller tests. Either you trust MVC's validation or write your own (i.e. don't test other's code, test your code)
    2. If you do want to test validation is doing what you expect, test it in your model tests (I do this for a couple of my more complex regex validations).

    What you really want to test here is that your controller does what you expect it to do when validation fails. That's your code, and your expectations. Testing it is easy once you realize that's all you want to test:

    [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));
    }
    
    0 讨论(0)
提交回复
热议问题