ASP.NET MVC Test

UPDATE: This article is rather old now – MVC Preview 3 is out and I suggest looking at the new testing stuff in that 🙂 http://weblogs.asp.net/scottgu/archive/2008/05/27/asp-net-mvc-preview-3-release.aspx

I’ve been playing with MVC for a couple of days now as part of my professional development at Readify – and this afternoon it was time to attack a bit of testing.

So I cracked open Scott’s post on MVC and zoomed in on the unit testing code he posted.

Now, because ASP.NET MVC is quite new (its CTP, not beta, not anything, just a preview – so its *real new*) the dev community is still poking around trying out this and that and in the case of testing, not having a real smooth ride.

In Scott’s post he uses a class called TestViewEngine, which allows him to instantiate and test the views defined in the test web app project, running asserts etc on the output. But this class doesn’t exist in the MVC stuff yet – that I can see anyway (or my Googling).

So I set out on a Google fest to try to get this test code to run… and it turns out it was just a little more difficult than just creating a TestViewEngine class. The final result make use of the excellent Rhino mocking framework and my own TestViewEngine implementation.

I got the mocking code from here: http://haacked.com/archive/2007/12/09/writing-unit-tests-for-controller-actions.aspx a great article on the initial state of MVC testing. I then combine this code with Scott’s and some of my own for the final result. You can download Rhino Mocks from here: http://www.ayende.com/projects/rhino-mocks/downloads.aspx.

There are also some great posts here http://www.persistall.com/ which may help you out a bit.

Basically the code is as on haacked for the most part:

 
 [TestMethod]
        public void AllForUser()
        {

            RouteTable.Routes.Add(new Route
            {
                Url = "Snippet/Detail/[id]",
                RouteHandler = typeof(MvcRouteHandler)
            });

            SnippetController controller = new SnippetController();

            MockRepository mocks = new MockRepository();
            IHttpContext httpContextMock = mocks.DynamicMock();
            IHttpRequest requestMock = mocks.DynamicMock();
            IHttpResponse responseMock = mocks.DynamicMock();
            SetupResult.For(httpContextMock.Request).Return(requestMock);
            SetupResult.For(httpContextMock.Response).Return(responseMock);
            SetupResult.For(requestMock.ApplicationPath).Return("/");

            responseMock.Redirect("/Snippet/Detail/1");

            RouteData routeData = new RouteData();
            routeData.Values.Add("Action", "SnippetDetail");
            routeData.Values.Add("Controller", "Snippet");

            ControllerContext contextMock = new
              ControllerContext(httpContextMock, routeData, controller);
            mocks.ReplayAll();

            controller.GetType().GetProperty("TempData").SetValue(controller, new TempDataDictionary(httpContextMock), null);

            controller.ControllerContext = contextMock;

            TestViewEngine tve = new TestViewEngine();

            controller.ViewFactory = tve;

            controller.SnippetDetail(1);

            Assert.AreEqual(typeof(Snippet), tve.View.ViewData.GetType(), "Snippet object passed to view");
            Assert.AreEqual(1, tve.View.GetViewData().SnippetId, "Correct Snippet ID Processed");
            Assert.AreEqual("SnippetDetail", tve.View.ViewName, "Correct view rendered");
        }

    }

The only thing I had to add here was the following line:

</pre>
<pre>controller.GetType().GetProperty("TempData").SetValue(controller, new TempDataDictionary(httpContextMock), null);

The problem was that the RenderView call in my ControllerAction was throwing a null argument exception because TempData on the object was null and is not allowed to be – thanks to a comment by Alexey on haacked for this!

Then I created my own little TestViewEngine class to keep in line with what Scott was doing in his post:

 

 public class TestViewEngine : IViewFactory
    {

        TestView _view;

        #region IView Members

        #endregion

        #region IViewFactory Members

        public IView CreateView(ControllerContext controllerContext, string viewName, string masterName, object viewData)
        {
            _view = new TestView(controllerContext, viewName, masterName, viewData);
            return _view;
        }

        public TestView View
        {
            get
            {
                return _view;
            }
        }

        #endregion
    }

    public class TestView : IView, IViewDataContainer
    {
        ControllerContext _controllerContext;
        string _viewName;
        string _masterName;
        object _viewData;

        public TestView(ControllerContext controllerContext, string viewName, string masterName, object viewData)
        {
            _controllerContext = controllerContext;
            _viewName = viewName;
            _masterName = masterName;
            _viewData = viewData;
        }

        public T GetViewData()
        {
            return (T)_viewData;
        }

        public string ViewName
        {
            get
            {
                return _viewName;
            }
        }

        #region IView Members

        public void RenderView(ViewContext viewContext)
        {
            //throw new NotImplementedException();
        }

        #endregion

        #region IViewDataContainer Members

        public object ViewData
        {
            get
            {
                return _viewData;
            }
        }

        #endregion
    }

With this code in place you should be able to correctly run the asserts as in Scott’s article.

From what I have read there are going to be a lot of additions in the coming versions that will assist with testing – hopefully this includes things to assist with mocking the View requests etc. For now at least we can run some testing code to get us through.

6 thoughts on “ASP.NET MVC Test

  1. hey thanks for the great post!really useful since i’ve been trying to implement unit testing with no success after trying scott gu’s tutorials.

    There just this error that i am getting:

    Error 2 ‘MvcApplicationTest.TestView’ does not implement interface member ‘System.Web.Mvc.IView.RenderView(System.Web.Mvc.ViewContext)’

    Could you please guide me to where my error could be found or solved.

    Thanks

  2. Hi Garth,

    Thanks for your comment.

    I would look at first sight like you are missing the following in your test view class.

    #region IView Members

    public void RenderView(ViewContext viewContext)
    {
    //throw new NotImplementedException();
    }

    Double check this and let me know how you get on…

  3. Hi,

    Great Article.

    I implement Scott Guru’s sample and added the code for TestViewEngine. But, when I ran the test for “Detail” method, it throws following exception:

    Test method MyStoreTest.Controllers.ProductsControllerTest.Detail threw exception: System.ArgumentNullException: Value cannot be null.
    Parameter name: controllerContext.

    Do you have any idea why this is occuring?

  4. Hi Sudarshan,

    Are you able to post the test code you are having problems with?

    In the code I posted, there is a line that passes the mock context object into the SnippetController class (which is derived from Controller)… controller.ControllerContext = contextMock;

    This is only a guess though without seeing your code.

  5. How do you format your code in your post?

    You have nice formatted code, do you use any tool to format your code.

    Thanks,
    J.W.

Comments are closed.