如何进行ASP.NET MVC 的测试
 
2009-01-15 作者:王德水 来源:cnblogs.com
 

本文参考了http://stephenwalther.com/blog/的内容。

今天需要对ASP.NET MVC的Controller进行测试,我们都知道当我们在测试工程里new一个controller时,这个controller里的httpcontext是空的,也就是session,cookie, form等都是空。

方法一:Mock controller的HttpContext, 暂时失败

那么我们如何对controller进行测试呢,我首先想到的是mock一个httpcontext,这里我用的是Rhino Mocks

public static class MvcMockHelpers

    {

        public static HttpContextBase FakeHttpContext(this MockRepository mocks)

        {

            HttpContextBase context = mocks.PartialMock<HttpContextBase>();

            HttpRequestBase request = mocks.PartialMock<HttpRequestBase>();

            HttpResponseBase response = mocks.PartialMock<HttpResponseBase>();

            HttpSessionStateBase session = mocks.PartialMock<HttpSessionStateBase>();

            HttpServerUtilityBase server = mocks.PartialMock<HttpServerUtilityBase>();

            SetupResult.For(context.Request).Return(request);

            SetupResult.For(context.Response).Return(response);               

            SetupResult.For(context.Session).Return(session);

            SetupResult.For(context.Server).Return(server);

            mocks.Replay(context);

            return context;

        }

        public static HttpContextBase FakeHttpContext(this MockRepository mocks, string url)

        {

            HttpContextBase context = FakeHttpContext(mocks);

            context.Request.SetupRequestUrl(url);

            return context;

        }

        public static void SetFakeControllerContext(this MockRepository mocks, Controller controller)

        {

            var httpContext = mocks.FakeHttpContext();

            ControllerContext context = new ControllerContext(new RequestContext(httpContext, new RouteData()), controller);

            controller.ControllerContext = context;

        }

下面我们建立一个ASP.NET MVC工程

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Web.Mvc;

using System.Web.Mvc.Ajax;

namespace MVCTest.Controllers

{

    [HandleError]

    public class HomeController : Controller

    {

        public ActionResult TestSession()

        {

            Session["name"] = "Jack Wang";

            ViewData["Title"] = "Home Page";

            ViewData["Message"] = "Welcome to ASP.NET MVC!";

            if (Session["CurrentCulture"] != null)

            {

                ViewData["CurrentCulture"] = Session["CurrentCulture"];

            }

            return View();

        }

        public ActionResult TestForm()

        {

            ViewData["Name"] = Request.Form["Name"];

            ViewData["Age"] = Request.Form["Age"];

            ViewData["count"] = Request.Form.Count;

            return View();

        }

        public ActionResult TestLogin()

        {

            if (User.Identity.IsAuthenticated)

            {

                ViewData["userName"] = User.Identity.Name;

                return View("Admin");

            }

            else

            {

                return RedirectToAction("Index");

            }

        }

        public ActionResult Admin()

        {

            if (User.IsInRole("Admin"))

            {

                return View("Admin");

            }

            else

            {

                return RedirectToAction("Index");

            }

        }

        public ViewResult Details()

        {

            ViewData["PageSize"] = Request.QueryString["PageSize"];

            ViewData["CurrentPage"] = Request.QueryString["CurrentPage"];

            ViewData["count"] = Request.QueryString.Count;

            return View();

        }

        public ViewResult TestCookie()

        {

            ViewData["key"] = Request.Cookies["key"].Value;

            return View();

        }

    }

}

测试代码

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Web.Mvc;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using MVCTest;

using MVCTest.Controllers;

using MvcFakes;

using System.Web.SessionState;

using System.Web;

using System.Collections.Specialized;

using Rhino.Mocks;

namespace MVCTest.Tests.Controllers

{

    /// <summary>

    /// Summary description for HomeControllerTestByMock

    /// </summary>

    [TestClass]

    public class HomeControllerTestByMock

    {

        public HomeControllerTestByMock()

        {

            //

            // TODO: Add constructor logic here

            //

        }

        private MockRepository mocks;

        [TestInitialize]

        public void Setup()

        {

            mocks = new MockRepository();

        }

        [TestMethod]

        public void TestSession()

        {

            // Arrange

            HomeController controller = new HomeController();

            mocks.SetFakeControllerContext(controller);

            controller.Session.Add("name", "Jack Wang");

            SetupResult.For(controller.Session["CurrentCulture"]).Return("zh-CN");

            mocks.ReplayAll();

            // Act

            ViewResult result = controller.TestSession() as ViewResult;

            // Assert

            ViewDataDictionary viewData = result.ViewData;

            Assert.AreEqual("Home Page", viewData["Title"]);

            Assert.AreEqual("Welcome to ASP.NET MVC!", viewData["Message"]);

            Assert.AreEqual(controller.Session["name"], "Jack Wang");

            Assert.AreEqual("zh-CN", viewData["CurrentCulture"]);

        }

    }

}

运行,测试

image

从错误信息可以看到是因为代码里Session["name"] = "Jack Wang"出错

本人排查很久,只知道mock的controllercontext的Session里没有这个Key,但现在还没有找到如何解决,哪位高人能帮吗解决?

二,自己写个模拟的Fake类,测试通过

既然这种方法不行,我们只能换一种方法,还好controller的ControllerContext可以赋值,这样我们就通过继承ControllerContext来Fake

using System;

using System.Collections;

using System.Collections.Generic;

using System.Collections.Specialized;

using System.Linq;

using System.Text;

using System.Web;

using System.Web.SessionState;

namespace MvcFakes

{

    public class FakeHttpSessionState : HttpSessionStateBase

    {

        private readonly SessionStateItemCollection _sessionItems;

        public FakeHttpSessionState(SessionStateItemCollection sessionItems)

        {

            _sessionItems = sessionItems;

        }

        public override void Add(string name, object value)

        {

            _sessionItems[name] = value;

        }

        public override int Count

        {

            get

            {

                return _sessionItems.Count;

            }

        }

        public override IEnumerator GetEnumerator()

        {

            return _sessionItems.GetEnumerator();

        }

        public override NameObjectCollectionBase.KeysCollection Keys

        {

            get

            {

                return _sessionItems.Keys;

            }

        }

        public override object this[string name]

        {

            get

            {

                return _sessionItems[name];

            }

            set

            {

                _sessionItems[name] = value;

            }

        }

        public override object this[int index]

        {

            get

            {

                return _sessionItems[index];

            }

            set

            {

                _sessionItems[index] = value;

            }

        }

        public override void Remove(string name)

        {

            _sessionItems.Remove(name);

        }

    }

}

using System;

using System.Collections.Specialized;

using System.Web;

namespace MvcFakes

{

    public class FakeHttpRequest : HttpRequestBase

    {

        private readonly NameValueCollection _formParams;

        private readonly NameValueCollection _queryStringParams;

        private readonly HttpCookieCollection _cookies;

        public FakeHttpRequest(NameValueCollection formParams, NameValueCollection queryStringParams, HttpCookieCollection cookies)

        {

            _formParams = formParams;

            _queryStringParams = queryStringParams;

            _cookies = cookies;

        }

        public override NameValueCollection Form

        {

            get

            {

                return _formParams;

            }

        }

        public override NameValueCollection QueryString

        {

            get

            {

                return _queryStringParams;

            }

        }

        public override HttpCookieCollection Cookies

        {

            get

            {

                return _cookies;

            }

        }

    }

}

using System;

using System.Security.Principal;

namespace MvcFakes

{

    public class FakeIdentity : IIdentity

    {

        private readonly string _name;

        public FakeIdentity(string userName)

        {

            _name = userName;

        }

        public string AuthenticationType

        {

            get { throw new System.NotImplementedException(); }

        }

        public bool IsAuthenticated

        {

            get { return !String.IsNullOrEmpty(_name); }

        }

        public string Name

        {

            get { return _name; }

        }

    }

}

using System;

using System.Linq;

using System.Security.Principal;

namespace MvcFakes

{

    public class FakePrincipal : IPrincipal

    {

        private readonly IIdentity _identity;

        private readonly string[] _roles;

        public FakePrincipal(IIdentity identity, string[] roles)

        {

            _identity = identity;

            _roles = roles;

        }

        public IIdentity Identity

        {

            get { return _identity; }

        }

        public bool IsInRole(string role)

        {

            if (_roles == null)

                return false;

            return _roles.Contains(role);

        }

    }

}

using System;

using System.Collections.Specialized;

using System.Security.Principal;

using System.Web;

using System.Web.SessionState;

namespace MvcFakes

{

    public class FakeHttpContext : HttpContextBase

    {

        private readonly FakePrincipal _principal;

        private readonly NameValueCollection _formParams;

        private readonly NameValueCollection _queryStringParams;

        private readonly HttpCookieCollection _cookies;

        private readonly SessionStateItemCollection _sessionItems;

        public FakeHttpContext(FakePrincipal principal, NameValueCollection formParams, NameValueCollection queryStringParams, HttpCookieCollection cookies, SessionStateItemCollection sessionItems )

        {

            _principal = principal;

            _formParams = formParams;

            _queryStringParams = queryStringParams;

            _cookies = cookies;

            _sessionItems = sessionItems;

        }

        public override HttpRequestBase Request

        {

            get

            {

                return new FakeHttpRequest(_formParams, _queryStringParams, _cookies);

            }

        }

        public override IPrincipal User

        {

            get

            {

                return _principal;

            }

            set

            {

                throw new System.NotImplementedException();

            }

        }

        public override HttpSessionStateBase Session

        {

            get

            {

                return new FakeHttpSessionState(_sessionItems);

            }

        }

    }

}

using System;

using System.Collections.Specialized;

using System.Web;

using System.Web.Mvc;

using System.Web.Routing;

using System.Web.SessionState;

namespace MvcFakes

{

    public class FakeControllerContext : ControllerContext

    {

        public FakeControllerContext(ControllerBase controller)

            : this(controller, null, null, null, null, null, null)

        {

        }

        public FakeControllerContext(ControllerBase controller, HttpCookieCollection cookies)

            : this(controller, null, null, null, null, cookies, null)

        {

        }

        public FakeControllerContext(ControllerBase controller, SessionStateItemCollection sessionItems)

            : this(controller, null, null, null, null, null, sessionItems)

        {

        }

        public FakeControllerContext(ControllerBase controller, NameValueCollection formParams)

            : this(controller, null, null, formParams, null, null, null)

        {

        }

        public FakeControllerContext(ControllerBase controller, NameValueCollection formParams, NameValueCollection queryStringParams)

            : this(controller, null, null, formParams, queryStringParams, null, null)

        {

        }

        public FakeControllerContext(ControllerBase controller, string userName)

            : this(controller, userName, null, null, null, null, null)

        {

        }

        public FakeControllerContext(ControllerBase controller, string userName, string[] roles)

            : this(controller, userName, roles, null, null, null, null)

        {

        }

        public FakeControllerContext

            (

                ControllerBase controller,

                string userName,

                string[] roles,

                NameValueCollection formParams,

                NameValueCollection queryStringParams,

                HttpCookieCollection cookies,

                SessionStateItemCollection sessionItems

            )

            : base(new FakeHttpContext(new FakePrincipal(new FakeIdentity(userName), roles), formParams, queryStringParams, cookies, sessionItems),

            new RouteData(), controller)

        { }

    }

}

下面是测试类

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Web.Mvc;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using MVCTest;

using MVCTest.Controllers;

using MvcFakes;

using System.Web.SessionState;

using System.Web;

using System.Collections.Specialized;

namespace MVCTest.Tests.Controllers

{

    /// <summary>

    /// Summary description for HomeControllerTest

    /// </summary>

    [TestClass]

    public class HomeControllerTest

    {

        [TestMethod]

        public void TestSession()

        {

            // Arrange

            HomeController controller = new HomeController();

            var sessionItems = new SessionStateItemCollection();

            sessionItems["CurrentCulture"] = "zh-CN";

            controller.ControllerContext = new FakeControllerContext(controller, sessionItems);

            // Act

            ViewResult result = controller.TestSession() as ViewResult;

            // Assert

            ViewDataDictionary viewData = result.ViewData;

            Assert.AreEqual("Home Page", viewData["Title"]);

            Assert.AreEqual("Welcome to ASP.NET MVC!", viewData["Message"]);

            Assert.AreEqual(sessionItems["name"], "Jack Wang");

            Assert.AreEqual("zh-CN", viewData["CurrentCulture"]);

        }

        [TestMethod]

        public void TestFakeFormParams()

        {

            var controller = new HomeController();

            var formParams = new NameValueCollection { { "Name", "Jack" }, { "Age", "28" } };

            controller.ControllerContext = new FakeControllerContext(controller, formParams);

            var result = controller.TestForm() as ViewResult;

            Assert.AreEqual("Jack", result.ViewData["Name"]);

            Assert.AreEqual("28", result.ViewData["Age"]);

            Assert.AreEqual(formParams.Count, result.ViewData["count"]);

        }

        [TestMethod]

        public void TestFakeQueryStringParams()

        {

            var controller = new HomeController();

            var queryStringParams = new NameValueCollection { { "PageSize", "10" }, { "CurrentPage", "5" } };

            controller.ControllerContext = new FakeControllerContext(controller, null, queryStringParams);

            var result = controller.Details() as ViewResult;

            Assert.AreEqual("10", result.ViewData["PageSize"]);

            Assert.AreEqual("5", result.ViewData["CurrentPage"]);

            Assert.AreEqual(queryStringParams.Count, result.ViewData["count"]);

        }

        [TestMethod]

        public void TestFakeUser()

        {

            var controller = new HomeController();

            controller.ControllerContext = new FakeControllerContext(controller, "Jack Wang");

            var result = controller.TestLogin() as ActionResult;

            Assert.IsInstanceOfType(result, typeof(ViewResult));

            ViewDataDictionary viewData = ((ViewResult)result).ViewData;

            Assert.AreEqual("Jack Wang", viewData["userName"]);

            controller.ControllerContext = new FakeControllerContext(controller);

            result = controller.TestLogin() as ActionResult;

            Assert.IsInstanceOfType(result, typeof(RedirectToRouteResult));

        }

        [TestMethod]

        public void TestFakeUserRoles()

        {

            var controller = new HomeController();

            controller.ControllerContext = new FakeControllerContext(controller, "Jack Wang", new string[] { "Admin" });

            var result = controller.Admin() as ActionResult;

            Assert.IsInstanceOfType(result, typeof(ViewResult));

            controller.ControllerContext = new FakeControllerContext(controller);

            result = controller.Admin() as ActionResult;

            Assert.IsInstanceOfType(result, typeof(RedirectToRouteResult));

        }

        [TestMethod]

        public void TestCookies()

        {

            var controller = new HomeController();

            var cookies = new HttpCookieCollection();

            cookies.Add(new HttpCookie("key", "a"));

            controller.ControllerContext = new FakeControllerContext(controller, cookies);

            var result = controller.TestCookie() as ViewResult;

            Assert.AreEqual("a", result.ViewData["key"]);

        }

    }

}

测试完全通过

image 

总结: 个人感觉ASP.NET MVC的测试是很不友好的,和ruby on rails相比,在测试这一块还有很大的距离,希望正式版里这一块能够加强

本文源码下载


火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。
资源网站: UML软件工程组织