在unit testing中设置HttpContext.Current.Session
我有一个Web服务,我试图unit testing。 在服务中,它从HttpContext中提取几个值,如下所示:
m_password = (string)HttpContext.Current.Session["CustomerId"]; m_userID = (string)HttpContext.Current.Session["CustomerUrl"];
在unit testing中,我使用简单的工作请求来创build上下文,如下所示:
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";
我得到空引用exception,说HttpContext.Current.Session为空。 有没有什么办法在unit testing中初始化当前会话?
我们必须使用HttpContextManager
来模拟HttpContext
,并在应用程序中调用工厂以及unit testing
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; } }
然后,您将使用HttpContextManager.Current
replace对HttpContext.Current
任何调用,并访问相同的方法。 那么当你testing的时候,你也可以访问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; }
然后在你的unit testing中使用它,我在我的testing初始化方法中调用它
HttpContextManager.SetCurrentContext(GetMockedHttpContext());
那么你可以在上面的方法中添加Session的预期结果,你希望可以使用你的Web服务。
你可以通过像这样创build一个新的HttpContext来“伪造”它:
我已经把这个代码放在一个静态帮助器类中,像这样:
public static HttpContext FakeHttpContext() { var httpRequest = new HttpRequest("", "http://stackoverflow/", ""); 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; }
或者不使用reflection来构build新的HttpSessionState
实例,只需将HttpSessionStateContainer
附加到HttpContext
(按照Brent M. Spell的注释):
SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);
然后你可以在你的unit testing中调用它:
HttpContext.Current = MockHelper.FakeHttpContext();
米洛克斯解决scheme比接受的一个恕我直言,但我有处理url与查询string时执行此问题的一些问题 。
我做了一些改变,使其与任何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; }
前段时间我对此有所了解。
unit testingMVC3 .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); }
如果你使用MVC框架,这应该工作。 我使用了Milox的 FakeHttpContext并添加了几行代码。 这个想法来自这个职位:
这似乎在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();
你可以试试FakeHttpContext :
using (new FakeHttpContext()) { HttpContext.Current.Session["CustomerId"] = "customer1"; }
和我一起工作的答案是@Anthony写的,但是你必须添加另外一行
request.SetupGet(req => req.Headers).Returns(new NameValueCollection());
所以你可以使用这个:
HttpContextFactory.Current.Request.Headers.Add(key, value);
在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>();
尝试这个:
// 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); } } }
这将允许您testing会话和caching。
我正在寻找一些比上面提到的选项更less侵入的东西。 最后,我提出了一个俗气的解决scheme,但可能会让一些人快一点。
首先我创build了一个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); } }
答案@Ro Hit给了我很多帮助,但我错过了用户凭证,因为我不得不伪造用户进行authenticationunit testing。 因此,让我来描述我是如何解决这个问题的。
据此,如果添加该方法
// 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
方法的最后一行,用户凭证将被添加并准备用于身份validationtesting。
我还注意到,你可能需要HttpContext中的其他部分,比如.MapPath()
方法。 有一个FakeHttpContext可用, 这里描述 ,可以通过NuGet安装。