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()
        {
           
        }
    }
}

 

这个是翻阅以前的一份笔记找出来的,年代久远,当时类似测试驱动开发,先在单元测试中实现逻辑,再写到正式的业务代码中,而项目经理通过验证单元测试通过率即可知道项目的基本情况(主流程要求全部需要单元测试);而且当进行业务改动时,相关单元测试会报错,然后开房人员就知道本次的修改会影响到其他的业务。现在感觉这样的开发形式比较少,维护单元测试的成本也比较大。


posted @ 2014-05-09 20:26  星星不说话  阅读(1024)  评论(0编辑  收藏  举报