Search code examples
asp.net-mvc-3unit-testingmockingquery-stringvalue-provider

Mock MVC3 ControllerContext query string collection and ValueProvider


I followed code sample in Steve Sanderson's MVC3 book, there is a piece of ControlContext mock code there I can setup form and query string values.

In order to support TryUpdateModel in unit test I made some change to the code, for example I have changed data type of formValues to FormCollection formValues and I added following codes to make it work:

        // form Values values setup
        this.Request.Setup(x => x.Form).Returns(formValues);

        // wire up form with value provider
        if (formValues != null)
        {
            onController.ValueProvider = formValues.ToValueProvider();
        }

Meanwhile I would like to do the same thing to querystring collection, but could not find equivalent class to support such a function.

Who has any idea what shall I do to query string?


Solution

  • Eventually, this is my solution

    using System;
    using System.Collections.Generic;
    using System.Web;
    using System.Web.Mvc;
    using System.Web.Routing;
    using Moq;
    
    /// <summary>
    /// A helper class for MVC projects' unit testing.
    /// </summary>
    public class ContextMocks
    {
        /// <summary>
        /// Initializes a new instance of the ContextMocks class.
        /// </summary>
        /// <param name="onController">The controller to mock.</param>
        /// <param name="identityName">The fake security identity name for controller.</param>
        /// <param name="queryStrings">Fake query string values of the Http context.</param>
        /// <param name="formValues">Fake form values of the Http context.</param>
        /// <param name="isNewSession">Enables us to mock if the session was created for the current thread or not.</param>
        public ContextMocks(Controller onController, string identityName, FormCollection queryStrings, FormCollection formValues, bool isNewSession = true)
        {
            this.Cookies = new HttpCookieCollection();
    
            this.Request = new Mock<HttpRequestBase>();
    
            this.Response = new Mock<HttpResponseBase>();
    
            this.Server = new Mock<HttpServerUtilityBase>();
    
            // Define all the common context objects, plus relationships between them
            this.HttpContext = new Mock<HttpContextBase>();
            this.HttpContext.Setup(x => x.Request).Returns(this.Request.Object);
            this.HttpContext.Setup(x => x.Response).Returns(this.Response.Object);
            this.HttpContext.Setup(x => x.Session).Returns(new FakeSessionState(isNewSession));
            this.HttpContext.Setup(x => x.Application).Returns(new FakeApplicationState());
            this.HttpContext.Setup(x => x.User.Identity.Name).Returns(identityName);
            this.HttpContext.Setup(x => x.Server).Returns(this.Server.Object);
    
            // cookie setup
            this.Request.Setup(x => x.Cookies).Returns(this.Cookies);
            this.Response.Setup(x => x.Cookies).Returns(this.Cookies);
    
            // query string setup
            this.Request.Setup(x => x.QueryString).Returns(queryStrings);
    
            // wire up form with value provider
            if (queryStrings != null)
            {
                onController.ValueProvider = queryStrings.ToValueProvider();
            }
    
            // form Values values setup
            this.Request.Setup(x => x.Form).Returns(formValues);
    
            // wire up form with value provider
            if (formValues != null)
            {
                onController.ValueProvider = formValues.ToValueProvider();
            }
    
            // Apply the mock context to the supplied controller instance
            RequestContext rc = new RequestContext(this.HttpContext.Object, new RouteData());
            onController.ControllerContext = new ControllerContext(rc, onController);
        }
    
        /// <summary>
        /// Initializes a new instance of the ContextMocks class. By using this constructor the mock will also enable UrlHelper mocking
        /// by using routing table setting.
        /// </summary>
        /// <param name="onController">The controller to mock.</param>
        /// <param name="registerRouteTable">
        /// A delegate to the function to register route table. 
        /// Typically it is MvcApplication.RegisterRoutes function in global.asax.cs.
        /// </param>
        /// <param name="identityName">The fake security identity name for controller.</param>
        /// <param name="queryStrings">Fake query string values of the Http context.</param>
        /// <param name="formValues">Fake form values of the Http context.</param>
        /// <param name="isNewSession">Enables us to mock if the session was created for the current thread or not.</param>
        public ContextMocks(
            Controller onController, 
            Action<RouteCollection> registerRouteTable, 
            string identityName, 
            FormCollection queryStrings, 
            FormCollection formValues,
            bool isNewSession = true)
            : this(onController, identityName, queryStrings, formValues, isNewSession)
        {
            this.Response.Setup(x => x.ApplyAppPathModifier(It.IsAny<string>())).Returns<string>(s => s);
    
            // Arrange (get the routing config and test context)
            RouteCollection routeConfig = new RouteCollection();
            registerRouteTable(routeConfig);
            onController.Url = new UrlHelper(new RequestContext(this.HttpContext.Object, new RouteData()), routeConfig);
        }
    
        /// <summary>
        /// Gets Http Context mock.
        /// </summary>
        public Mock<HttpContextBase> HttpContext { get; private set; }
    
        /// <summary>
        /// Gets Http request mock.
        /// </summary>
        public Mock<HttpRequestBase> Request { get; private set; }
    
        /// <summary>
        /// Gets Http response mock.
        /// </summary>
        public Mock<HttpResponseBase> Response { get; private set; }
    
        /// <summary>
        /// Gets the mock server object.
        /// </summary>
        public Mock<HttpServerUtilityBase> Server { get; private set; }
    
        /// <summary>
        /// Gets Route data.
        /// </summary>
        public RouteData RouteData { get; private set; }
    
        /// <summary>
        /// Gets or sets A collection used to hold fake cookie values.
        /// </summary>
        private HttpCookieCollection Cookies
        {
            get;
            set;
        }
    
        /// <summary>
        /// Use queryStringCollectionProvider fake HttpSessionStateBase, because it's hard to mock it with Moq.
        /// </summary>
        private class FakeSessionState : HttpSessionStateBase
        {
            /// <summary>
            /// Enables us to mock whether the session was created for the current thread or not.
            /// </summary>
            private bool isNewSession;
    
            /// <summary>
            /// Fake session state collection.
            /// </summary>
            private Dictionary<string, object> items = new Dictionary<string, object>();
    
            /// <summary>
            /// Initializes a new instance of the FakeSessionState class.
            /// </summary>
            /// <param name="isNewSession">Enables us to mock if the session was created for the current thread or not.</param>
            public FakeSessionState(bool isNewSession)
            {
                this.isNewSession = isNewSession;
            }           
    
            /// <summary>
            /// Gets a value that indicates whether the session was created during the current request.
            /// </summary>
            public override bool IsNewSession
            {
                get
                {
                    return this.isNewSession;
                }
            }
    
            /// <summary>
            /// An indexer to access the fake session item.
            /// </summary>
            /// <param name="name">Name of fake session key.</param>
            /// <returns>Fake session value.</returns>
            public override object this[string name]
            {
                get { return this.items.ContainsKey(name) ? this.items[name] : null; }
                set { this.items[name] = value; }
            }
    
            /// <summary>
            /// Empties the contents of the session.
            /// </summary>
            public override void Abandon()
            {
                this.items = new Dictionary<string, object>();
            }
        }
    
        /// <summary>
        /// Use queryStringCollectionProvider fake HttpApplicationStateBase, because it's hard to mock it with Moq.
        /// </summary>
        private class FakeApplicationState : HttpApplicationStateBase
        {
            /// <summary>
            /// Fake application state collection.
            /// </summary>
            private Dictionary<string, object> items = new Dictionary<string, object>();
    
            /// <summary>
            /// An indexer to access the fake application item.
            /// </summary>
            /// <param name="name">Name of fake application key.</param>
            /// <returns>Fake application value.</returns>
            public override object this[string name]
            {
                get { return this.items.ContainsKey(name) ? this.items[name] : null; }
                set { this.items[name] = value; }
            }
    
            /// <summary>
            /// Mock out of the lock method.
            /// </summary>
            public override void Lock()
            {
            }
    
            /// <summary>
            /// Mock out of the unlock method.
            /// </summary>
            public override void UnLock()
            {
            }
    
            /// <summary>
            /// Adds the fake object to the application object.
            /// </summary>
            /// <param name="name">The key</param>
            /// <param name="value">The value being added in conjunction with the key.</param>
            public override void Add(string name, object value)
            {
                this.items.Add(name, value);
            }
        }
    }