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


185

我有一个要尝试进行单元测试的Web服务。在服务中,它从HttpContext类似的方法中提取几个值,如下所示:

 m_password = (string)HttpContext.Current.Session["CustomerId"];
 m_userID = (string)HttpContext.Current.Session["CustomerUrl"];

在单元测试中,我正在使用简单的工作程序请求创建上下文,如下所示:

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";

我得到的null引用异常HttpContext.Current.Session为null。

有什么办法可以在单元测试中初始化当前会话?


您尝试过这种方法吗?
拉吉·兰詹

如果可以,请使用HttpContextBase
jrummell 2012年

Answers:


105

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

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.CurrentHttpContextManager.Current并有机会获得同样的方法。然后,当您进行测试时,您还可以访问HttpContextManager并模拟您的期望

这是使用Moq的示例:

private HttpContextBase GetMockedHttpContext()
{
    var context = new Mock<HttpContextBase>();
    var request = new Mock<HttpRequestBase>();
    var response = new Mock<HttpResponseBase>();
    var session = new Mock<HttpSessionStateBase>();
    var server = new Mock<HttpServerUtilityBase>();
    var user = new Mock<IPrincipal>();
    var identity = new Mock<IIdentity>();
    var urlHelper = new Mock<UrlHelper>();

    var routes = new RouteCollection();
    MvcApplication.RegisterRoutes(routes);
    var requestContext = new Mock<RequestContext>();
    requestContext.Setup(x => x.HttpContext).Returns(context.Object);
    context.Setup(ctx => ctx.Request).Returns(request.Object);
    context.Setup(ctx => ctx.Response).Returns(response.Object);
    context.Setup(ctx => ctx.Session).Returns(session.Object);
    context.Setup(ctx => ctx.Server).Returns(server.Object);
    context.Setup(ctx => ctx.User).Returns(user.Object);
    user.Setup(ctx => ctx.Identity).Returns(identity.Object);
    identity.Setup(id => id.IsAuthenticated).Returns(true);
    identity.Setup(id => id.Name).Returns("test");
    request.Setup(req => req.Url).Returns(new Uri("http://www.google.com"));
    request.Setup(req => req.RequestContext).Returns(requestContext.Object);
    requestContext.Setup(x => x.RouteData).Returns(new RouteData());
    request.SetupGet(req => req.Headers).Returns(new NameValueCollection());

    return context.Object;
}

然后在单元测试中使用它,我在Test Init方法中调用它

HttpContextManager.SetCurrentContext(GetMockedHttpContext());

然后,您可以在上述方法中,将期望从Session中获得的预期结果添加到Web服务中。


1
但这不使用SimpleWorkerRequest
knocte 2012年

他试图模拟HttpContext,以便他的SimpleWorkerRequest可以访问HttpContext中的值,他将在服务中使用HttpContextFactory
Anthony Shaw

是否有意仅对模拟上下文(通过SetCurrentContext进行设置时)返回支持字段m_context,而对于真正的HttpContext而言,将为对Current的每次调用创建包装器?
Stephen Price

是的。m_context类型为HttpContextBase,返回HttpContextWrapper返回带有当前HttpContext的HttpContextBase
Anthony Shaw

1
HttpContextManager会比起一个更好的名字,HttpContextSource但我同意这HttpContextFactory是一种误导。
编程教授

298

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

http://www.necronet.org/archive/2010/07/28/unit-testing-code-that-uses-httpcontext-current-session.aspx

我已经将该代码放入了如下的静态帮助器类中:

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,也可以不使用反射来构建新实例,而只需将其附加HttpSessionStateContainerHttpContext(按照Brent M. Spell的评论):

SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);

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

HttpContext.Current = MockHelper.FakeHttpContext();

24
我喜欢此答案,而不是公认的答案,因为更改生产代码以支持测试活动是错误的做法。当然,您的生产代码应该抽象出这样的第三方名称空间,但是当您使用旧代码时,您并不总是拥有此控件或重构的奢侈。
肖恩·格洛弗

29
您不必使用反射来构造新的HttpSessionState实例。您可以使用SessionStateUtility.AddHttpSessionStateToContext将HttpSessionStateContainer附加到HttpContext。
布伦特M.Spell 2012年

MockHelper只是静态方法所在的类的名称,您可以使用任何喜欢的名称。
Milox

我已尝试实现您的答案,但Session仍然为null。您能否看一下我的Poststackoverflow.com/questions/23586765/…。谢谢
2014年

Server.MapPath()如果您也使用它,将无法正常工作。
Yuck

45

Milox解决方案比公认的恕直言更好,但在处理带有querystring的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;
}

这让您伪造httpContext.Session,任何想法该怎么做httpContext.Application
KyleMit

39

不久前,我对此有些担心。

MVC3 .NET中的单元测试HttpContext.Current.Session

希望能帮助到你。

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

工作原理如此简单简单……谢谢!
mggSoft

12

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

http://codepaste.net/p269t8

这似乎在MVC 5中有效。在早期版本的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();

3
链接已断开,因此下次可以将代码放在这里。
Rhyous

11

您可以尝试FakeHttpContext

using (new FakeHttpContext())
{
   HttpContext.Current.Session["CustomerId"] = "customer1";       
}

效果很好,而且使用
起来

8

在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();

https://stackoverflow.com/a/34022964/516748

考虑使用 Moq

new Mock<ISession>();

7

与我一起工作的答案是@Anthony写的,但是您必须添加另一行是

    request.SetupGet(req => req.Headers).Returns(new NameValueCollection());

因此您可以使用以下代码:

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

2

试试这个:

        // 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

并添加类:

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

这将允许您同时测试会话和缓存。


1

我正在寻找比上面提到的选项更具侵入性的东西。最后,我提出了一个俗气的解决方案,但它可能会使某些人的移动速度更快一些。

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

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

然后,我向控制器的构造函数添加了一个可选参数。如果存在该参数,则将其用于会话操作。否则,请使用HttpContext.Session:

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

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

class MyControllerTest
{

    private readonly MyController _controller;

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

我真的很喜欢您的解决方案。吻=>保持简单和愚蠢;-)
CodeNotFound

1

永远不要嘲笑..永远不要!解决方案非常简单。为什么要伪造如此美丽的作品HttpContext呢?

将会话下推!(这一行足以让我们大多数人理解,但下面会详细说明)

(string)HttpContext.Current.Session["CustomerId"];是我们现在访问它的方式。更改为

_customObject.SessionProperty("CustomerId")

从测试中调用时,_customObject使用备用存储(DB或云键值[ http://www.kvstore.io/]

但是当从实际应用程序调用时,_customObject使用Session

这是怎么做的?好吧……依赖注入!

因此,test可以设置会话(地下),然后调用应用程序方法,就好像它对会话一无所知。然后测试秘密检查应用程序代码是否正确更新了会话。或者,如果应用程序的行为基于测试设置的会话值。

实际上,即使我说过:“从不嘲笑”,我们还是以嘲笑告终。因为我们情不自禁地走到下一条规则,“嘲笑伤害最小的地方!”。嘲笑巨大的HttpContext或嘲笑一个很小的会话,这伤害最小吗?不要问我这些规则从何而来。让我们只说常识。这是关于不嘲笑的有趣读物,因为单元测试会杀死我们


0

@Ro Hit给出的答案对我有很大帮助,但是我缺少了用户凭据,因为我不得不假冒用户进行身份验证单元测试。因此,让我描述一下我是如何解决的。

根据这个,如果添加的方法

    // 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安装。



0

尝试这种方式。

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;
  }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.