Mock session,cookie,querystring in ASB.NET MVC

  写测试用例的时候经常发现,所写的功能需要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 的嘲讽.

参考链接:

ASP.NET MVC Tip #12 – Faking the Controller Context


ASP.NET MVC, HttpContext.Current is null while mocking a request

上一篇:GCD调度组、自定义队列来实现多个下载任务


下一篇:Git版本控制:Git冲突解决 相关错误总结