C# 在单元测试中设置HttpContext.Current.Session

C# 在单元测试中设置HttpContext.Current.Session,c#,web-services,unit-testing,httpcontext,C#,Web Services,Unit Testing,Httpcontext,我有一个web服务,我正在尝试进行单元测试。在服务中,它从HttpContext中提取多个值,如下所示: m_password = (string)HttpContext.Current.Session["CustomerId"]; m_userID = (string)HttpContext.Current.Session["CustomerUrl"]; 在单元测试中,我使用一个简单的worker请求创建上下文,如下所示: SimpleWorkerRequest request = ne

我有一个web服务,我正在尝试进行单元测试。在服务中,它从
HttpContext
中提取多个值,如下所示:

 m_password = (string)HttpContext.Current.Session["CustomerId"];
 m_userID = (string)HttpContext.Current.Session["CustomerUrl"];
在单元测试中,我使用一个简单的worker请求创建上下文,如下所示:

SimpleWorkerRequest request = new SimpleWorkerRequest("", "", "", null, new StringWriter());
HttpContext context = new HttpContext(request);
HttpContext.Current = context;
但是,每当我尝试设置
HttpContext.Current.Session的值时

HttpContext.Current.Session["CustomerId"] = "customer1";
HttpContext.Current.Session["CustomerUrl"] = "customer1Url";
我得到的空引用异常表示
HttpContext.Current.Session
为空


有没有办法在单元测试中初始化当前会话?

我们必须使用
HttpContextManager
模拟
HttpContext
,并从应用程序和单元测试中调用工厂

public class HttpContextManager 
{
    private static HttpContextBase m_context;
    public static HttpContextBase Current
    {
        get
        {
            if (m_context != null)
                return m_context;

            if (HttpContext.Current == null)
                throw new InvalidOperationException("HttpContext not available");

            return new HttpContextWrapper(HttpContext.Current);
        }
    }

    public static void SetCurrentContext(HttpContextBase context)
    {
        m_context = context;
    }
}

然后,您可以将对
HttpContext.Current
的任何调用替换为
HttpContextManager.Current
,并可以访问相同的方法。然后,当您进行测试时,还可以访问
HttpContextManager
并模拟您的期望

这是一个使用以下内容的示例:

然后,您可以在上面的方法中添加您希望web服务可用的会话的预期结果。

您可以通过创建一个新的
HttpContext来“伪造它”,如下所示:

我将该代码放在一个静态助手类上,如下所示:

public static HttpContext FakeHttpContext()
{
    var httpRequest = new HttpRequest("", "http://example.com/", "");
    var stringWriter = new StringWriter();
    var httpResponse = new HttpResponse(stringWriter);
    var httpContext = new HttpContext(httpRequest, httpResponse);

    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 });

    return httpContext;
}
或者不使用反射来构造新的
HttpSessionState
实例,您只需将
httpsessionstatecainer
附加到
HttpContext
(根据Brent M.Spell的注释):

然后您可以在单元测试中调用它,如:

HttpContext.Current = MockHelper.FakeHttpContext();

与我合作的答案是@Anthony写的,但你必须添加另一行

    request.SetupGet(req => req.Headers).Returns(new NameValueCollection());
所以你可以用这个:

HttpContextFactory.Current.Request.Headers.Add(key, value);

我刚才听说了这件事

希望能有帮助

[TestInitialize]
public void TestSetup()
{
    // We need to setup the Current HTTP Context as follows:            

    // Step 1: Setup the HTTP Request
    var httpRequest = new HttpRequest("", "http://localhost/", "");

    // Step 2: Setup the HTTP Response
    var httpResponce = new HttpResponse(new StringWriter());

    // Step 3: Setup the Http Context
    var httpContext = new HttpContext(httpRequest, httpResponce);
    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 });

    // Step 4: Assign the Context
    HttpContext.Current = httpContext;
}

[TestMethod]
public void BasicTest_Push_Item_Into_Session()
{
    // Arrange
    var itemValue = "RandomItemValue";
    var itemKey = "RandomItemKey";

    // Act
    HttpContext.Current.Session.Add(itemKey, itemValue);

    // Assert
    Assert.AreEqual(HttpContext.Current.Session[itemKey], itemValue);
}
比我所能接受的要好

我做了一些更改,使它能够正确地处理任何URL,并避免反射

public static HttpContext FakeHttpContext(string url)
{
    var uri = new Uri(url);
    var httpRequest = new HttpRequest(string.Empty, uri.ToString(),
                                        uri.Query.TrimStart('?'));
    var stringWriter = new StringWriter();
    var httpResponse = new HttpResponse(stringWriter);
    var httpContext = new HttpContext(httpRequest, httpResponse);

    var sessionContainer = new HttpSessionStateContainer("id",
                                    new SessionStateItemCollection(),
                                    new HttpStaticObjectsCollection(),
                                    10, true, HttpCookieMode.AutoDetect,
                                    SessionStateMode.InProc, false);

    SessionStateUtility.AddHttpSessionStateToContext(
                                         httpContext, sessionContainer);

    return httpContext;
}

如果您使用的是MVC框架,那么这应该是可行的。我使用了FakeHttpContext并添加了几行额外的代码。这个想法来自这篇文章:

这似乎在MVC5中起作用。我还没有在早期版本的MVC中尝试过这一点

HttpContext.Current = MockHttpContext.FakeHttpContext();

var wrapper = new HttpContextWrapper(HttpContext.Current);

MyController controller = new MyController();
controller.ControllerContext = new ControllerContext(wrapper, new RouteData(), controller);

string result = controller.MyMethod();
您可以尝试:

试试这个:

        // MockHttpSession Setup
        var session = new MockHttpSession();

        // MockHttpRequest Setup - mock AJAX request
        var httpRequest = new Mock<HttpRequestBase>();

        // Setup this part of the HTTP request for AJAX calls
        httpRequest.Setup(req => req["X-Requested-With"]).Returns("XMLHttpRequest");

        // MockHttpContextBase Setup - mock request, cache, and session
        var httpContext = new Mock<HttpContextBase>();
        httpContext.Setup(ctx => ctx.Request).Returns(httpRequest.Object);
        httpContext.Setup(ctx => ctx.Cache).Returns(HttpRuntime.Cache);
        httpContext.Setup(ctx => ctx.Session).Returns(session);

        // MockHttpContext for cache
        var contextRequest = new HttpRequest("", "http://localhost/", "");
        var contextResponse = new HttpResponse(new StringWriter());
        HttpContext.Current = new HttpContext(contextRequest, contextResponse);

        // MockControllerContext Setup
        var context = new Mock<ControllerContext>();
        context.Setup(ctx => ctx.HttpContext).Returns(httpContext.Object);

        //TODO: Create new controller here
        //      Set controller's ControllerContext to context.Object
//MockHttpSession设置
var session=new MockHttpSession();
//MockHttpRequest设置-模拟AJAX请求
var httpRequest=new Mock();
//为AJAX调用设置HTTP请求的这一部分
httpRequest.Setup(req=>req[“X-request-With”])。返回(“XMLHttpRequest”);
//MockHttpContextBase设置-模拟请求、缓存和会话
var httpContext=new Mock();
Setup(ctx=>ctx.Request).Returns(httpRequest.Object);
Setup(ctx=>ctx.Cache).Returns(HttpRuntime.Cache);
Setup(ctx=>ctx.Session).Returns(Session);
//缓存的MockHttpContext
var contextRequest=newhttprequest(“,”http://localhost/", "");
var contextResponse=new-HttpResponse(new-StringWriter());
当前=新的HttpContext(contextRequest,contextResponse);
//MockControllerContext设置
var context=newmock();
Setup(ctx=>ctx.HttpContext).Returns(HttpContext.Object);
//TODO:在此处创建新控制器
//将控制器的ControllerContext设置为context.Object
并添加类:

public class MockHttpSession : HttpSessionStateBase
{
    Dictionary<string, object> _sessionDictionary = new Dictionary<string, object>();
    public override object this[string name]
    {
        get
        {
            return _sessionDictionary.ContainsKey(name) ? _sessionDictionary[name] : null;
        }
        set
        {
            _sessionDictionary[name] = value;
        }
    }

    public override void Abandon()
    {
        var keys = new List<string>();

        foreach (var kvp in _sessionDictionary)
        {
            keys.Add(kvp.Key);
        }

        foreach (var key in keys)
        {
            _sessionDictionary.Remove(key);
        }
    }

    public override void Clear()
    {
        var keys = new List<string>();

        foreach (var kvp in _sessionDictionary)
        {
            keys.Add(kvp.Key);
        }

        foreach(var key in keys)
        {
            _sessionDictionary.Remove(key);
        }
    }
}
公共类MockHttpSession:HttpSessionStateBase
{
字典_sessionDictionary=新字典();
公共重写对象[字符串名称]
{
得到
{
返回_sessionDictionary.ContainsKey(名称)?_sessionDictionary[name]:null;
}
设置
{
_sessionDictionary[name]=值;
}
}
公共覆盖无效放弃()
{
var keys=新列表();
foreach(var kvp in_sessionDictionary)
{
Key.Add(kvp.Key);
}
foreach(var键入键)
{
_sessionDictionary.Remove(键);
}
}
公共覆盖无效清除()
{
var keys=新列表();
foreach(var kvp in_sessionDictionary)
{
Key.Add(kvp.Key);
}
foreach(var键入键)
{
_sessionDictionary.Remove(键);
}
}
}

这将允许您同时使用会话和缓存进行测试。

在asp.net Core/MVC 6 rc2中,您可以设置
HttpContext

var SomeController controller = new SomeController();

controller.ControllerContext = new ControllerContext();
controller.ControllerContext.HttpContext = new DefaultHttpContext();
controller.HttpContext.Session = new DummySession();
rc 1为

var SomeController controller = new SomeController();

controller.ActionContext = new ActionContext();
controller.ActionContext.HttpContext = new DefaultHttpContext();
controller.HttpContext.Session = new DummySession();

考虑使用
Moq

new Mock<ISession>();
newmock();
答案给了我很多帮助,但我丢失了用户凭据,因为我不得不伪造用户进行身份验证单元测试。因此,让我描述一下我是如何解决它的

根据,如果添加该方法

    // using System.Security.Principal;
    GenericPrincipal FakeUser(string userName)
    {
        var fakeIdentity = new GenericIdentity(userName);
        var principal = new GenericPrincipal(fakeIdentity, null);
        return principal;
    }
然后追加

    HttpContext.Current.User = FakeUser("myDomain\\myUser");
在您完成的
TestSetup
方法的最后一行,添加了用户凭据并准备用于身份验证测试


我还注意到,在HttpContext中可能需要其他部分,例如
.MapPath()
方法。有一个FakeHttpContext可用,可以通过NuGet安装。

我正在寻找比上面提到的选项入侵性稍小的东西。最后,我想出了一个俗气的解决方案,但它可能会让一些人走得更快一些

首先,我创建了一个TestSession类:

现在,我可以将TestSession注入控制器:

class MyControllerTest
{

    private readonly MyController _controller;

    public MyControllerTest()
    {
        var testSession = new TestSession();
        var _controller = new MyController(testSession);
    }
}

我找到了以下在HttpContext中指定用户的简单解决方案:

Never mock。。从未!解决方案非常简单。为什么要伪造像
HttpContext
这样美丽的作品

将会话向下推!(仅此一行就足以让我们大多数人理解,但将在下面详细解释)

(字符串)HttpContext.Current.Session[“CustomerId”]是我们现在访问它的方式。将此更改为

_customObject.SessionProperty("CustomerId")
从测试调用时,
    // using System.Security.Principal;
    GenericPrincipal FakeUser(string userName)
    {
        var fakeIdentity = new GenericIdentity(userName);
        var principal = new GenericPrincipal(fakeIdentity, null);
        return principal;
    }
    HttpContext.Current.User = FakeUser("myDomain\\myUser");
class TestSession : ISession
{

    public TestSession()
    {
        Values = new Dictionary<string, byte[]>();
    }

    public string Id
    {
        get
        {
            return "session_id";
        }
    }

    public bool IsAvailable
    {
        get
        {
            return true;
        }
    }

    public IEnumerable<string> Keys
    {
        get { return Values.Keys; }
    }

    public Dictionary<string, byte[]> Values { get; set; }

    public void Clear()
    {
        Values.Clear();
    }

    public Task CommitAsync()
    {
        throw new NotImplementedException();
    }

    public Task LoadAsync()
    {
        throw new NotImplementedException();
    }

    public void Remove(string key)
    {
        Values.Remove(key);
    }

    public void Set(string key, byte[] value)
    {
        if (Values.ContainsKey(key))
        {
            Remove(key);
        }
        Values.Add(key, value);
    }

    public bool TryGetValue(string key, out byte[] value)
    {
        if (Values.ContainsKey(key))
        {
            value = Values[key];
            return true;
        }
        value = new byte[0];
        return false;
    }
}
class MyController
{

    private readonly ISession _session;

    public MyController(ISession session = null)
    {
        _session = session;
    }


    public IActionResult Action1()
    {
        Session().SetString("Key", "Value");
        View();
    }

    public IActionResult Action2()
    {
        ViewBag.Key = Session().GetString("Key");
        View();
    }

    private ISession Session()
    {
        return _session ?? HttpContext.Session;
    }
}
class MyControllerTest
{

    private readonly MyController _controller;

    public MyControllerTest()
    {
        var testSession = new TestSession();
        var _controller = new MyController(testSession);
    }
}
_customObject.SessionProperty("CustomerId")
public static HttpContext getCurrentSession()
  {
        HttpContext.Current = new HttpContext(new HttpRequest("", ConfigurationManager.AppSettings["UnitTestSessionURL"], ""), new HttpResponse(new System.IO.StringWriter()));
        System.Web.SessionState.SessionStateUtility.AddHttpSessionStateToContext(
        HttpContext.Current, new HttpSessionStateContainer("", new SessionStateItemCollection(), new HttpStaticObjectsCollection(), 20000, true,
        HttpCookieMode.UseCookies, SessionStateMode.InProc, false));
        return HttpContext.Current;
  }