写测试用例的时候经常发现,所写的功能需要Http上下文的支持(session,cookie)这类的.
以下介绍2种应用场景.
控制器内的Requet其实是控制器内的属性.那么在mock的时候把那些上下文附加到Controller里的控制器上下文(ControllerContext )里,request自然就有东西了.
public Controller() { /// <summary> /// 获取或设置控制器上下文。 /// </summary> /// /// <returns> /// 控制器上下文。 /// </returns> public ControllerContext ControllerContext { get; set; } /// <summary> /// 为当前 HTTP 请求获取 HttpRequestBase 对象。 /// </summary> /// /// <returns> /// 请求对象。 /// </returns> public HttpRequestBase Request { get { if (this.HttpContext != null) return this.HttpContext.Request; return (HttpRequestBase) null; } } }
为此,为了单独的Mock这些http上下文中的一些元素,我们需要6个类
//http://stephenwalther.com/archive/2008/07/01/asp-net-mvc-tip-12-faking-the-controller-context public class FakeControllerContext : ControllerContext { //public FakeControllerContext(ControllerBase controller) // : this(controller, null, null, null, null, null, null) //{ //} /// <summary> /// MockCookie /// </summary> /// <param name="controller"></param> /// <param name="cookies"></param> public FakeControllerContext(ControllerBase controller, HttpCookieCollection cookies) : this(controller, null, null, null, null, cookies, null) { } /// <summary> /// MockSession /// </summary> /// <param name="controller"></param> /// <param name="sessionItems"></param> public FakeControllerContext(ControllerBase controller, SessionStateItemCollection sessionItems) : this(controller, null, null, null, null, null, sessionItems) { } /// <summary> /// MockForm /// </summary> /// <param name="controller"></param> /// <param name="formParams"></param> public FakeControllerContext(ControllerBase controller, NameValueCollection formParams) : this(controller, null, null, formParams, null, null, null) { } /// <summary> /// MockForm+QueryString /// </summary> /// <param name="controller"></param> /// <param name="formParams"></param> /// <param name="queryStringParams"></param> 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) { } /// <summary> /// Mock Session+Cookie+Form+QuertyString+IIdentity /// </summary> /// <param name="controller">控制器名</param> /// <param name="userName"></param> /// <param name="roles"></param> /// <param name="formParams">Form</param> /// <param name="queryStringParams">QueryString</param> /// <param name="cookies">Cookie</param> /// <param name="sessionItems">Session</param> 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) { } /// <summary> /// /// </summary> /// <param name="controller"></param> /// <param name="formParams"></param> /// <param name="queryStringParams"></param> /// <param name="cookies"></param> /// <param name="sessionItems"></param> /// <param name="userName"></param> /// <param name="roles"></param> public FakeControllerContext ( ControllerBase controller, NameValueCollection formParams, NameValueCollection queryStringParams, HttpCookieCollection cookies, SessionStateItemCollection sessionItems, string userName = null, string[] roles = null ) : base(new FakeHttpContext( new FakePrincipal(new FakeIdentity(userName), roles), formParams, queryStringParams, cookies, sessionItems), new RouteData(), controller) { } } 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); } } } 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; } } } 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); } } 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; } } } 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); } }
在原示例里面那个外国佬还mock了其他东西( IPrincipal User).但对于我来说没这方面需求.
然后我们测试一下.
public class TestController : Controller { #region 请求模拟输出 public ActionResult TestSession() { return Content(Session["hehe"].ToString()); } public ActionResult TestCookie() { var cookie = Request.Cookies["hehe"]; if (cookie == null) return new EmptyResult(); return Content(cookie.Values["c1"]); } #endregion #region 请求测试 public ActionResult TestForm() { string fuckyou = Request.Form["fuckyou"]; if (fuckyou == null) return new EmptyResult(); return Content(fuckyou); } public ActionResult TestFormAndQueryString() { string form = Request.Form["fuckyou"]; string querty = Request.QueryString["fuckyou2"]; return Content(form + "," + querty); } public ActionResult TestMuilt() { var session = Session["hehe"].ToString(); var cookie = Request.Cookies["hehe"].Values["c1"]; string fuckyou = Request.Form["fuckyou"]; string querty = Request.QueryString["fuckyou2"]; return Content(string.Format("{1} {0} {2} {0}{3} {0} {4} {0}", Environment.NewLine, session, cookie, fuckyou, querty)); } #endregion }
测试类
[TestClass] public class MockRequestTest { private readonly IUserCenterService _IUserCenterService; public MockRequestTest() { EngineContext.Initialize(false); _IUserCenterService = EngineContext.Current.Resolve<IUserCenterService>(); } [Test] [TestMethod] public void MockSession() { //_IUserCenterService = EngineContext.Current.Resolve<IUserCenterService>(); var controller = new TestController(); var sessionItems = new SessionStateItemCollection(); sessionItems["hehe"] = 23; controller.ControllerContext = new FakeControllerContext(controller, sessionItems); var result = controller.TestSession() as ContentResult; Assert.AreEqual(result.Content, "23"); } [TestMethod] public void MockCookie() { var controller = new TestController(); var mockCookie = new HttpCookie("hehe"); mockCookie["c1"] = "nima1"; mockCookie["c2"] = "nima2"; var requestCookie = new HttpCookieCollection() { { mockCookie } }; controller.ControllerContext = new FakeControllerContext(controller, requestCookie); var result = controller.TestCookie() as ContentResult; Console.WriteLine(HttpContext.Current == null); Assert.AreEqual("nima1", result.Content); } /// <summary> /// MockForm /// </summary> [TestMethod] public void MockForm() { var controller = new TestController(); NameValueCollection form = new FormCollection() { {"fuckyou","1"}, {"fuckyou","2"}, }; controller.ControllerContext = new FakeControllerContext(controller, form); var result = controller.TestForm() as ContentResult; Debug.Assert(false, result.Content); Assert.IsNotNull(result.Content); } /// <summary> /// MockForm /// </summary> [TestMethod] public void MockFormAndQueryString() { var controller = new TestController(); NameValueCollection form = new FormCollection() { {"fuckyou","1"}, {"fuckyou2","2"}, }; controller.ControllerContext = new FakeControllerContext(controller, form, form); var result = controller.TestFormAndQueryString() as ContentResult; //Debug.Assert(false, result.Content); Assert.AreEqual("1,2", result.Content); } /// <summary> /// Mock Session+Cookie+Form+QuertyString /// </summary> [TestMethod] public void MockMuilt() { var controller = new TestController(); var sessionItems = new SessionStateItemCollection(); sessionItems["hehe"] = 23; var mockCookie = new HttpCookie("hehe"); mockCookie["c1"] = "nima1"; mockCookie["c2"] = "nima2"; var requestCookie = new HttpCookieCollection() { { mockCookie } }; NameValueCollection form = new FormCollection() { {"fuckyou","1"}, {"fuckyou2","2"}, }; controller.ControllerContext = new FakeControllerContext(controller, form, form, requestCookie, sessionItems); var result = controller.TestMuilt() as ContentResult; Debug.Assert( false, result.Content, string.Format("正确的结果顺序应该是{0};{1};{2};{3};", sessionItems[0], mockCookie["c1"], form["fuckyou"], form["fuckyou2"]) ); } }
在上面这个MS测试用例里,我分别测试了
都是通过的.
但是这样有个问题.
问题就是: 然而这并没有什么卵用.
实际开发的时候.控制器基本打酱油,别的层面需要获取上下文是从HttpContext.Current.Request中获取.如果在刚才的测试用例.控制器输出的是HttpContext.Current.Request.这玩意无疑是null的.因为我们只是把上下文赋值到控制器里的http上下文里面,和HttpContext.Current.Reques是不同的一个概念.
所以呢,我们需要mock 和HttpContext.Current.Request.
session的话,比较容易,那就是
SessionStateUtility.AddHttpSessionStateToContext
cookie的话比较麻烦.HttpRequest.Cookies是一个只读属性,就算用反射赋值也会失败.这里我比较取巧,只用了cookie集合的第一个.有多个的话,可能得把方法改得更恶心一点吧.
public static class WebExtension { /// <summary> /// 伪造session /// </summary> /// <param name="url"></param> /// <param name="sesion"></param> /// <param name="queryString"></param> /// <param name="requesttype"></param> public static void FakeHttpContext(this string url, SessionStateItemCollection sesion, string queryString = null, string requesttype = null, HttpCookieCollection cookie = null) { var stringWriter = new StringWriter(); var httpResponce = new HttpResponse(stringWriter); HttpRequest request; if (cookie == null) { request = new HttpRequest(string.Empty, url, queryString ?? string.Empty) { RequestType = requesttype ?? "GET", }; } else { request = new HttpRequest(string.Empty, url, queryString ?? string.Empty) { RequestType = requesttype ?? "GET", Cookies = { cookie[0] }, }; } var httpContext = new HttpContext(request, httpResponce); if (sesion != null) { SessionStateUtility.AddHttpSessionStateToContext(httpContext, new HttpSessionStateContainer(SessionNameStorage.Suser, sesion, new HttpStaticObjectsCollection(), 20000, true, HttpCookieMode.AutoDetect, SessionStateMode.InProc, false )); } if (cookie != null) { //无法对只读属性赋值,会导致异常 //Type ret = typeof(HttpRequest); //PropertyInfo pr = ret.GetProperty("Cookies"); //pr.SetValue(request, cookie, null); //赋值属性 } //var sessionContainer = new HttpSessionStateContainer( // "id", // new SessionStateItemCollection(), // new HttpStaticObjectsCollection(), // 10, // true, // HttpCookieMode.AutoDetect, // SessionStateMode.InProc, // false); //httpContext.Items["AspSession"] = // typeof(HttpSessionState).GetConstructor( // BindingFlags.NonPublic | BindingFlags.Instance, // null, // CallingConventions.Standard, // new[] { typeof(HttpSessionStateContainer) }, // null).Invoke(new object[] { sessionContainer }); HttpContext.Current = httpContext; } }
public ActionResult TestHttpCurrent() { var a = System.Web.HttpContext.Current; if (a != null) { return Content(a.Request.Cookies.Get("hehe").Value); } return Content(""); } [TestMethod] public void httpCurrent() { var controller = new TestController(); var mockCookie = new HttpCookie("hehe"); mockCookie["c1"] = "nima1"; mockCookie["c2"] = "nima2"; var requestCookie = new HttpCookieCollection() { { mockCookie } }; string.Format("{0}/test/TestHttpCurrent", TestHelper.WebRootUrl).FakeHttpContext(sesion: null, cookie: requestCookie); var result = controller.TestHttpCurrent() as ContentResult; Console.WriteLine(result.Content); }
session就不测了,我平时测试的时候试了无数次都是有的.
mock cookie那里,如果有更好的实现方式,请告诉我.
标题是故意为之的,代表了我对ASB.NET 的嘲讽.
ASP.NET MVC Tip #12 – Faking the Controller Context
ASP.NET MVC, HttpContext.Current is null while mocking a request