Twitter Feed Popout byInfofru

OverrideThis.com

Adventures in .NET Software Craftsmanship!

MVC2 Model Validation and Testing Scenarios

The solution described in this blog doesn't work for all scenarios.
Navigate to the following post for a better solution:
http://blog.overridethis.com/blog/post/2010/07/08/MVC2-Validation-and-Testing-e28093-Refactored.aspx

* TIP: If you don’t know what MVC2 Model Validation is you can check out this video here.

 

Lately, I have begun using ASP.NET MVC2 Model validation as much as possible and I have noticed a significant drop in the amount of plumbing code for validating a user’s request, which is making my controller action’s lighter, more readable and easier to manage. 

 

The one big drawback of using MVC2 Model Validation is that you are no longer able to test the functionality of your Controller’s action methods without having to mock or stub the validation process.  This is due to the fact that validation occurs during the Model binding process and as such is done by the ModelBinder, which doesn’t get called if you test your controller action’s by calling them directly.  We could write code to ensure that the Model binding process happens but it would be painful as we would have to mock around with the ControllerContext which is not an easy endeavor.

 

The following is my outline for a possible solution to this problem inspired by this post.  

 

First, lets establish a context to solve the problem, the following code snippet shows a Controller, Model, and Service Interface that all are direct dependencies of the CommentController we want to test.

 

Exhibit A – Very Simple Controller, Service, and Model:

namespace WebSite.Services {

   public interface ICommentService {
        void AddComment(string message);
    }

}
namespace WebSite.Models {
    
    using System.ComponentModel.DataAnnotations;

    public class AddCommentRequest {
        [Required]
        public string Message { get; set; }
    }

    public class AddCommentResult {
        public bool Success { get; set; }
        public string Message { get; set; }
    }
}
namespace WebSite.Controllers {

    using System.Web.Mvc;
    using Models;
    using Services;

    public class CommentController : Controller {

        private readonly ICommentService service;

        public CommentController() {

        }

        public CommentController(ICommentService service) {
            this.service = service;
        }

        [HttpPost]
        public ActionResult AddComment(AddCommentRequest request) {
            if (ModelState.IsValid) {
                this.service.AddComment(request.Message);
                return Json(new AddCommentResult {Success = true, Message = "Success"});
            }
            return Json(
                new AddCommentResult { 
                        Success = false, 
                        Message = "The 'message' is a required field." 
                });
        }
    }
}

 

Now, if we run the following tests the invalid request scenario would fail due to the validation not being triggered and as such the ModelState.IsValid property would always be true.

 

Exhibit B – The Tests

[TestFixture]
public class CommentControllerTests {

    private string VALID_MESSAGE = "SOME MESSAGE!";
    private string NOT_VALID_MESSAGE = string.Empty;

    [Test]
    public void CommentControllerCanAddValidMessage() {
        Assert.IsTrue(this.HttpPostToAddComments(VALID_MESSAGE).Success);
    }

    [Test]
    public void CommentControllerCannotAddValidMessage() {
        Assert.IsFalse(this.HttpPostToAddComments(NOT_VALID_MESSAGE).Success);
    }

    private AddCommentResult HttpPostToAddComments(string message) {
        
        var mockOfICommentService = new Mock<ICommentService>();
        mockOfICommentService.Setup(m => m.AddComment(VALID_MESSAGE)).AtMostOnce();
        var model = new AddCommentRequest {Message = message};
        var controller = new CommentController(mockOfICommentService.Object);
        var result = controller.AddComment(model);
        mockOfICommentService.Verify();
        return (AddCommentResult)((JsonResult) result).Data;
    }
}

 

The best option to solve this issue and keep our tests light would be to write an extension method that would allow us to call the Controller Action elegantly while adding the additional validation that needs to be stubbed out.  The following, is a possible implementation of that extension method.  One thing to note is that this implementation would only work in .NET 4.0 scenarios that are using System.Component.DataAnnotations attributes.  You could implement a similar solution in .NET 3.5 but would not have the ValidationContext or ValidationResult objects to leverage.

 

Exhibit C – The Controller Extension for validating System.Component.DataAnnotations

public static class ControllerExtensions {

    public static ActionResult CallWithModelValidation<C,R,T>(this C controller
            , Func<C,R> action
            , T model) 
        where C : Controller
        where R : ActionResult
        where T : class {

        var validationContext = new ValidationContext(model, null, null);
        var validationResults = new List<ValidationResult>();
        Validator.TryValidateObject(model, validationContext, validationResults);
        foreach (var validationResult in validationResults) {
            controller
                .ModelState
                .AddModelError(validationResult.MemberNames.First(), 
                    validationResult.ErrorMessage);
        }

        return action(controller);
    }
}

 

Having this extension method in place would allow us to rewrite just a single line of code in the tests to ensure everything is working as expected.

 

Exhibit D – The corrected test scenarios

[TestFixture]
public class CommentControllerTests {

    private string VALID_MESSAGE = "SOME MESSAGE!";
    private string NOT_VALID_MESSAGE = string.Empty;

    [Test]
    public void CommentControllerCanAddValidMessage() {
        Assert.IsTrue(this.HttpPostToAddComments(VALID_MESSAGE).Success);
    }

    [Test]
    public void CommentControllerCannotAddValidMessage() {
        Assert.IsFalse(this.HttpPostToAddComments(NOT_VALID_MESSAGE).Success);
    }

    private AddCommentResult HttpPostToAddComments(string message) {
        
        var mockOfICommentService = new Mock<ICommentService>();
        mockOfICommentService.Setup(m => m.AddComment(VALID_MESSAGE)).AtMostOnce();
        var model = new AddCommentRequest {Message = message};
        var controller = new CommentController(mockOfICommentService.Object);
        var result = controller.CallWithModelValidation(c => c.AddComment(model), model);
        mockOfICommentService.Verify();
        return (AddCommentResult)((JsonResult) result).Data;
    }
}

ASP.NET MVC 1.0 Released!!!

I am very excited to announce that Microsoft has officially released the RTM version of the ASP.NET MVC Framework during the Mix'09 developer's conference.  Make sure to download as soon as possible, and welcome to the ASP.NET MVC Framework generation.

Thanks for Reading,

Roberto Hernandez

Additional Links
(Download) http://www.microsoft.com/downloads/details.aspx?FamilyID=53289097-73ce-43bf-b6a6-35e00103cb4b&displaylang=en
(Official Website) http://www.asp.net/mvc

Ramblings on ASP.NET MVC - A new start!

The following is a link to a free PDF book chapter on ASP.NET MVC.  It is no secret that I am a big fan of ASP.NET MVC and I believe that it is one of the key new technologies that we should all be looking into sooner rather than later.

 

http://weblogs.asp.net/scottgu/archive/2009/03/10/free-asp-net-mvc-ebook-tutorial.aspx

 

The following is a list of my personal beliefs regarding ASP.NET MVC and how I think it currently affects my day to day decisions as an application architect and developer.

  • I believe it is easier to code with a strict separation of concerns using the ASP.NET MVC Framework.
    • Duh! It is one of the goals of the MVC Pattern, but I still think it should be mentioned since this makes ASP.NET MVC the appropriate technology to use when working with multi-tiered enterprise applications.
  • I believe it is easier to develop a Web 2.0 application using the ASP.NET MVC Framework.
    • No viewstate, and strange ASP.NET WebForms control renderings that get in the way of using current web standards and top of the line AJAX frameworks like jquery, extjs, yui, and prototype.
  • I believe it is easier to implement, and manage security in an ASP.NET MVC application.
    • There is less exposure as security is managed at the controller method instead of the .aspx page.
    • Implementing security trimming as an attribute rocks!! It is not a new idea by any means, but it should be noted that it provides you with the ability to move your security configuration out of the Web.Config file without having to go code crazy.
  • I don't believe ASP.NET MVC is an environment for building RAD applications.  So for small/medium projects and applications that are less prone to change over time, I would still recommend using ASP.NET WebForms.
    Warning: I reserve the right to change my mind completely in the near future.
    • No current UI designer support.
    • No existing third party controls available.
    • No need to be an Architecture Astronaut.
    • Exception: If you are building a RIA application it makes perfect sense to use ASP.NET MVC as your JSON web services tier and you can use any third party controls developed for your presentation tier technology (Silverlight, Flex, Flash, Air, Ajax, etc.) while still maintaining a solid architecture.

 

Have fun reading the book,

 

Roberto Hernandez