【问题标题】:Setting HttpContext.Current.Session in a unit test在单元测试中设置 HttpContext.Current.Session
【发布时间】:2012-03-26 07:47:22
【问题描述】:

我有一个我正在尝试进行单元测试的 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";

我得到空引用异常,指出 HttpContext.Current.Session 为空。

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

【问题讨论】:

标签: c# web-services unit-testing httpcontext


【解决方案1】:

我们必须使用 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 并模拟您的期望

这是一个使用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());

然后,您可以在上述方法中添加您期望可用于您的 Web 服务的 Session 的预期结果。

【讨论】:

  • 但这不使用 SimpleWorkerRequest
  • 他试图模拟 HttpContext,以便他的 SimpleWorkerRequest 可以访问 HttpContext 中的值,他将在他的服务中使用 HttpContextFactory
  • 是否有意只为模拟上下文(通过 SetCurrentContext 设置时)返回支持字段 m_context,而对于真正的 HttpContext,每次调用 Current 时都会创建一个包装器?
  • 是的。 m_context 是 HttpContextBase 类型,返回 HttpContextWrapper 会返回带有当前 HttpContext 的 HttpContextBase
  • HttpContextManager 会比 HttpContextSource 更好,但我同意 HttpContextFactory 具有误导性。
【解决方案2】:

您可以通过创建一个新的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 实例,您可以将HttpSessionStateContainer 附加到HttpContext(根据 Brent M. Spell 的评论):

SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);

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

HttpContext.Current = MockHelper.FakeHttpContext();

【讨论】:

  • 我比接受的答案更喜欢这个答案,因为更改您的生产代码以支持您的测试活动是不好的做法。当然,您的生产代码应该像这样抽象出第 3 方命名空间,但是当您使用遗留代码时,您并不总是拥有这种控制权或重构的奢侈。
  • 您不必使用反射来构造新的 HttpSessionState 实例。您可以使用 SessionStateUtility.AddHttpSessionStateToContext 将 HttpSessionStateContainer 附加到 HttpContext。
  • MockHelper 只是静态方法所在类的名称,你可以使用任何你喜欢的名称。
  • 我已尝试实施您的答案,但 Session 仍然为空。请你看看我的帖子stackoverflow.com/questions/23586765/…。谢谢
  • Server.MapPath() 如果你使用它也不会工作。
【解决方案3】:

对我有用的答案是@Anthony 写的,但你必须添加另一行

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

所以你可以使用这个:

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

【讨论】:

    【解决方案4】:

    我不久前写过一些关于这个的东西。

    Unit Testing HttpContext.Current.Session in MVC3 .NET

    希望对你有帮助。

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

    【讨论】:

    • 工作得很好很简单...谢谢!
    【解决方案5】:

    Milox solution 比公认的恕我直言更好,但I had some problems with this implementation when handling urls with 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做同样的事情吗?
    【解决方案6】:

    如果您使用的是 MVC 框架,这应该可以。我使用了Milox's 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();
    

    【讨论】:

    • 链接坏了,下次可以把代码放在这里。
    【解决方案7】:

    你可以试试FakeHttpContext:

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

    【讨论】:

    • 效果很好,使用非常简单
    • 很遗憾与 .NET Core 不兼容
    • @LuisGouveia,.NET Core 有这样的问题吗?
    【解决方案8】:

    试试这个:

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

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

    【讨论】:

      【解决方案9】:

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

      【讨论】:

        【解决方案10】:

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

        根据this,如果添加方法

            // 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 可用,它是described here,可以通过 NuGet 安装。

        【讨论】:

          【解决方案11】:

          我一直在寻找比上述选项更具侵略性的东西。最后我想出了一个俗气的解决方案,但它可能会让一些人行动得更快一点。

          首先我创建了一个 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);
              }
          }
          

          【讨论】:

          • 我真的很喜欢你的解决方案。 KISS => 保持简单和愚蠢 ;-)
          【解决方案12】:

          我找到了以下在 HttpContext 中指定用户的简单解决方案:https://forums.asp.net/post/5828182.aspx

          【讨论】:

            【解决方案13】:

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

            下推会话! (这一行就足够我们大多数人理解了,下面会详细解释)

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

            _customObject.SessionProperty("CustomerId")
            

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

            但在实际应用程序中调用时,_customObject 使用 Session

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

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

            实际上,即使我说:“从不嘲笑”,我们最终还是会嘲笑。因为我们情不自禁地滑到了下一条规则,“模拟伤害最小的地方!”。嘲笑巨大的HttpContext 或嘲笑一个小会议,哪个伤害最小?不要问我这些规则是从哪里来的。让我们只说常识。这是一篇关于不嘲笑as unit test can kills us

            的有趣读物

            【讨论】:

              【解决方案14】:

              试试这个方法..

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2016-11-03
                • 1970-01-01
                • 1970-01-01
                • 2016-03-29
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多