我们在开发WEB项目的时候,一般应用逻辑跟ASPX页面是分离的项目。应用逻辑一般会是一个DLL组件项目。如果这个组件项目中A方法使用了Session、Cookie等信息的读写,则这个方法就很难写单元测试。 但并不是写不出来,要写出来大致思路如下:
目标: 构建一个测试的环境,把需要的Session、Cookie等信息初始化好。 这样才好做测试。而且这个构建的环境,不应该影响实际功能代码的编写。
具体实现来说:
我们要使用Mock技术,但就HttpContext来言,直接mock这个对象会有一个问题,它不具备Session的功能。这时候我们就需要用 Mock 技术来构造一个可以满足我们需要的环境的原理:这个Mock的机制如下:
用反射机制,构造一个 HttpSessionState 对象(HttpSessionState类的构造函数是internal 的),然后把这个对象跟SimpleWorkerRequest 对象捆绑。
这样我们就可以 构建了一个满足自己需要的环境了,即 TestHttpContext 类。
以下是两个类的实现:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Web.SessionState;using System.Web;using System.Threading;using System.Globalization;using System.Collections.Specialized;using System.Collections;using System.IO;using System.Web.Hosting;using System.Reflection;
namespace TestNamespace{ public class TestHttpContext { private const string ContextKeyAspSession = "AspSession"; private HttpContext context = null; private TestHttpContext() : base() { } public TestHttpContext(bool isSecure) : this() { MySessionState myState = new MySessionState(Guid.NewGuid().ToString("N"), new SessionStateItemCollection(), new HttpStaticObjectsCollection(), 5, true, HttpCookieMode.UseUri, SessionStateMode.InProc, false);
TextWriter tw = new StringWriter(); HttpWorkerRequest wr = new SimpleWorkerRequest("/webapp", "c:\\inetpub\\wwwroot\\webapp\\", "default.aspx", "", tw); this.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; this.context.Items[ContextKeyAspSession] = state; HttpContext.Current = this.context; }
public HttpContext Context { get { return this.context; } }
private class WorkerRequest : SimpleWorkerRequest { private bool isSecure = false; public WorkerRequest(string page, string query, TextWriter output, bool isSecure) : base(page, query, output) { this.isSecure = isSecure; }
public override bool IsSecure() { return this.isSecure; } } } 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; } }
}
}
这样我们在进行单元测试时就可以Mock掉Web下的Session,Cookie等对象。
例如:
[TestMethod()]public void TestGetUserId(){ TestHttpContext mock = new TestHttpContext(false); System.Web.HttpContext context = mock.Context; context.Session["UserId"] = 1245;
Assert.AreEqual(long.Parse(context.Session["UserId"].ToString()), 1245, "读取用户ID错误!");}
转载于:https://www.cnblogs.com/wangjq/archive/2010/10/18/1854297.html