寫測試用例的時候經常發現,所寫的功能需要Http上下文的支援(session,cookie)這類的.
以下介紹2種應用場景.
用于控制器内Requet擷取參數
控制器内的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個類
Mock類
//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測試用例裡,我分别測試了
- Mock session
- Mock cookie
- Mock表單
- Mock 表單+querystring
- Mock session+cookie+表單+querystring
都是通過的.
但是這樣有個問題.
問題就是:然而這并沒有什麼卵用.
mock HttpContext.Current
實際開發的時候.控制器基本打醬油,别的層面需要擷取上下文是從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 的嘲諷.