本文参考了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相比,在测试这一块还有很大的距离,希望正式版里这一块能够加强
本文源码下载
|