如何进行ASP.NET MVC 的测试
本文参考了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"]);
}
}
}
运行,测试
从错误信息可以看到是因为代码里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"]);
}
}
}
测试完全通过
总结: 个人感觉ASP.NET MVC的测试是很不友好的,和ruby on rails相比,在测试这一块还有很大的距离,希望正式版里这一块能够加强
王德水 写于2008-01-05 00:30分