MVC中单元测试模拟HttpContext.Current.Session的问题
刚开始学习MVC1.0的时候,碰到一个单元测试问题。当时的场景在Controller里面使用了Session的数据,用户登录后将用户信息保存在Session中,大概是这样的场景吧,然后需要写一个单元测试模拟用户登录后的环境进行业务操作。在单元测试中,是不存在真正的Web环境的,即Session是不会真正被创建的,因此,必须模拟这个环境
首先是需要一个实现IHttpSessionState接口的类,然后实例化这个类 。
// 创建一个实现IHttpSessionState接口的类
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Globalization;
using System.Text;
using System.Threading;
using System.Web;
using System.Web.SessionState;
namespace SxchangWebTest
{
public sealed class MySessionState : IHttpSessionState
{
const int MAX_TIMEOUT = 24 * 60; // Timeout cannot exceed 24 hours.
string pId;
ISessionStateItemCollection pSessionItems;
HttpStaticObjectsCollection pStaticObjects;
int pTimeout;
bool pNewSession;
HttpCookieMode pCookieMode;
SessionStateMode pMode;
bool pAbandon;
bool pIsReadonly;
public MySessionState(string id,
ISessionStateItemCollection sessionItems,
HttpStaticObjectsCollection staticObjects,
int timeout,
bool newSession,
HttpCookieMode cookieMode,
SessionStateMode mode,
bool isReadonly)
{
pId = id;
pSessionItems = sessionItems;
pStaticObjects = staticObjects;
pTimeout = timeout;
pNewSession = newSession;
pCookieMode = cookieMode;
pMode = mode;
pIsReadonly = isReadonly;
}
public int Timeout
{
get { return pTimeout; }
set
{
if (value <= 0)
throw new ArgumentException("Timeout value must be greater than zero.");
if (value > MAX_TIMEOUT)
throw new ArgumentException("Timout cannot be greater than " + MAX_TIMEOUT.ToString());
pTimeout = value;
}
}
public string SessionID
{
get { return pId; }
}
public bool IsNewSession
{
get { return pNewSession; }
}
public SessionStateMode Mode
{
get { return pMode; }
}
public bool IsCookieless
{
get { return CookieMode == HttpCookieMode.UseUri; }
}
public HttpCookieMode CookieMode
{
get { return pCookieMode; }
}
// Abandon marks the session as abandoned. The IsAbandoned property is used by the
// session state module to perform the abandon work during the ReleaseRequestState event.
public void Abandon()
{
pAbandon = true;
}
public bool IsAbandoned
{
get { return pAbandon; }
}
// Session.LCID exists only to support legacy ASP compatibility. ASP.NET developers should use
// Page.LCID instead.
public int LCID
{
get { return Thread.CurrentThread.CurrentCulture.LCID; }
set { Thread.CurrentThread.CurrentCulture = CultureInfo.ReadOnly(new CultureInfo(value)); }
}
// Session.CodePage exists only to support legacy ASP compatibility. ASP.NET developers should use
// Response.ContentEncoding instead.
public int CodePage
{
get
{
if (HttpContext.Current != null)
return HttpContext.Current.Response.ContentEncoding.CodePage;
else
return Encoding.Default.CodePage;
}
set
{
if (HttpContext.Current != null)
HttpContext.Current.Response.ContentEncoding = Encoding.GetEncoding(value);
}
}
public HttpStaticObjectsCollection StaticObjects
{
get { return pStaticObjects; }
}
public object this[string name]
{
get { return pSessionItems[name]; }
set { pSessionItems[name] = value; }
}
public object this[int index]
{
get { return pSessionItems[index]; }
set { pSessionItems[index] = value; }
}
public void Add(string name, object value)
{
pSessionItems[name] = value;
}
public void Remove(string name)
{
pSessionItems.Remove(name);
}
public void RemoveAt(int index)
{
pSessionItems.RemoveAt(index);
}
public void Clear()
{
pSessionItems.Clear();
}
public void RemoveAll()
{
Clear();
}
public int Count
{
get { return pSessionItems.Count; }
}
public NameObjectCollectionBase.KeysCollection Keys
{
get { return pSessionItems.Keys; }
}
public IEnumerator GetEnumerator()
{
return pSessionItems.GetEnumerator();
}
public void CopyTo(Array items, int index)
{
foreach (object o in items)
items.SetValue(o, index++);
}
public object SyncRoot
{
get { return this; }
}
public bool IsReadOnly
{
get { return pIsReadonly; }
}
public bool IsSynchronized
{
get { return false; }
}
}
}
// 实例化MySessionState,并初创化
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.Hosting;
using System.Web.SessionState;
namespace SxchangWebTest
{
public static class MockHttpContext
{
private const string ContextKeyAspSession = "AspSession";
private static HttpContext context = null;
public static void Init()
{
MySessionState myState = new MySessionState(Guid.NewGuid().ToString("N"),
new SessionStateItemCollection(), new HttpStaticObjectsCollection(),
5, true, HttpCookieMode.UseUri, SessionStateMode.InProc, false);
TextWriter tw = new StringWriter();
// 这个地方是可以修改的,这是设置的Web路径的地方,但文件是可以不存在的
HttpWorkerRequest wr = new SimpleWorkerRequest("/webapp", "c://inetpub//wwwroot//webapp//", "default.aspx", "", tw);
context = new HttpContext(wr);
HttpSessionState state = Activator.CreateInstance(
typeof(HttpSessionState),
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Instance | BindingFlags.CreateInstance,
null,
new object[] { myState },
CultureInfo.CurrentCulture) as HttpSessionState;
context.Items[ContextKeyAspSession] = state;
HttpContext.Current = context;
}
public static HttpContext Context
{
get
{
return context;
}
}
}
}
然后是单元测试
// 简单的单元测试类
using NUnit.Framework;
namespace SxchangWebTest
{
[TestFixture]
public class MyHttpContextTest
{
[SetUp]
public void Setup()
{
MockHttpContext.Init();
}
[Test]
public void MySessionTest()
{
//System.Web.HttpContext context = mock.Context;
System.Web.HttpContext.Current.Session["aaa"] = "chang";
Assert.AreEqual("chang", (string)System.Web.HttpContext.Current.Session["aaa"]);
Assert.IsNotNull(System.Web.HttpContext.Current.Server);
Assert.IsNotNull(System.Web.HttpContext.Current.Request.FilePath);
Assert.IsNotNull(System.Web.HttpContext.Current.Response);
}
[TearDown]
public void Teardown()
{
}
}
}
这个是翻阅以前的一份笔记找出来的,年代久远,当时类似测试驱动开发,先在单元测试中实现逻辑,再写到正式的业务代码中,而项目经理通过验证单元测试通过率即可知道项目的基本情况(主流程要求全部需要单元测试);而且当进行业务改动时,相关单元测试会报错,然后开房人员就知道本次的修改会影响到其他的业务。现在感觉这样的开发形式比较少,维护单元测试的成本也比较大。


浙公网安备 33010602011771号