Configuración de HttpContext.Current.Session en una prueba de unidad

Tengo un servicio web que estoy intentando probar en una unidad. En el servicio, extrae varios valores del HttpContext así:

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

en la prueba unitaria, estoy creando el contexto usando una simple solicitud de trabajo, como esta:

 SimpleWorkerRequest request = new SimpleWorkerRequest("", "", "", null, new StringWriter()); HttpContext context = new HttpContext(request); HttpContext.Current = context; 

Sin embargo, cada vez que bash establecer los valores de HttpContext.Current.Session

 HttpContext.Current.Session["CustomerId"] = "customer1"; HttpContext.Current.Session["CustomerUrl"] = "customer1Url"; 

Obtengo una excepción de referencia nula que dice que HttpContext.Current.Session es nulo.

¿Hay alguna manera de inicializar la sesión actual dentro de la prueba unitaria?

Tuvimos que HttpContext de HttpContext utilizando un HttpContextManager y llamando a la fábrica desde nuestra aplicación, así como a las Pruebas unitarias

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

Luego reemplazaría cualquier llamada a HttpContext.Current con HttpContextManager.Current y tendrá acceso a los mismos métodos. Luego, cuando esté probando, también puede acceder al HttpContextManager y burlarse de sus expectativas

Este es un ejemplo que usa Moq :

 private HttpContextBase GetMockedHttpContext() { var context = new Mock(); var request = new Mock(); var response = new Mock(); var session = new Mock(); var server = new Mock(); var user = new Mock(); var identity = new Mock(); var urlHelper = new Mock(); var routes = new RouteCollection(); MvcApplication.RegisterRoutes(routes); var requestContext = new Mock(); 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; } 

y luego usarlo dentro de las pruebas de su unidad, lo llamo dentro de mi método Test Init

 HttpContextManager.SetCurrentContext(GetMockedHttpContext()); 

Entonces, en el método anterior, puede agregar los resultados esperados de la sesión que espera que estén disponibles para su servicio web.

Puedes “simularlo” creando un nuevo HttpContext como este:

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

Tomé ese código y lo puse en una clase de ayuda estática como esta:

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

O en lugar de usar el reflection para construir la nueva instancia de HttpSessionState , puede simplemente adjuntar su HttpSessionStateContainer al HttpContext (según el comentario de Brent M. Spell):

 SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer); 

y luego puedes llamarlo en las pruebas de tu unidad como:

 HttpContext.Current = MockHelper.FakeHttpContext(); 

La solución Milox es mejor que la aceptada en mi humilde opinión, pero tuve algunos problemas con esta implementación cuando manejaba urls con querystring .

Hice algunos cambios para que funcione correctamente con las URL y para evitar la reflexión.

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

Hice algo al respecto hace un tiempo.

Prueba unitaria HttpContext.Current.Session en MVC3 .NET

Espero eso ayude.

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

Si está utilizando el marco MVC, esto debería funcionar. Utilicé FakeHttpContext de Milox y agregué algunas líneas adicionales de código. La idea vino de esta publicación:

http://codepaste.net/p269t8

Esto parece funcionar en MVC 5. No lo he probado en versiones anteriores de 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(); 

Puedes probar FakeHttpContext :

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

La respuesta que funcionó conmigo es lo que @Anthony había escrito, pero tienes que agregar otra línea que es

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

para que puedas usar esto:

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

En asp.net Core / MVC 6 rc2 puede configurar el HttpContext

 var SomeController controller = new SomeController(); controller.ControllerContext = new ControllerContext(); controller.ControllerContext.HttpContext = new DefaultHttpContext(); controller.HttpContext.Session = new DummySession(); 

RC 1 era

 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

Considera usar Moq

 new Mock(); 

Prueba esto:

  // MockHttpSession Setup var session = new MockHttpSession(); // MockHttpRequest Setup - mock AJAX request var httpRequest = new Mock(); // 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(); 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(); context.Setup(ctx => ctx.HttpContext).Returns(httpContext.Object); //TODO: Create new controller here // Set controller's ControllerContext to context.Object 

Y agrega la clase:

 public class MockHttpSession : HttpSessionStateBase { Dictionary _sessionDictionary = new Dictionary(); 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(); 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(); foreach (var kvp in _sessionDictionary) { keys.Add(kvp.Key); } foreach(var key in keys) { _sessionDictionary.Remove(key); } } } 

Esto le permitirá probar con sesión y caché.

Estaba buscando algo un poco menos invasivo que las opciones mencionadas anteriormente. Al final se me ocurrió una solución cursi, pero podría hacer que algunas personas se muevan un poco más rápido.

Primero creé una clase TestSession :

 class TestSession : ISession { public TestSession() { Values = new Dictionary(); } public string Id { get { return "session_id"; } } public bool IsAvailable { get { return true; } } public IEnumerable Keys { get { return Values.Keys; } } public Dictionary 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; } } 

Luego agregué un parámetro opcional al constructor de mi controlador. Si el parámetro está presente, úselo para la manipulación de la sesión. De lo contrario, use 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; } } 

Ahora puedo inyectar mi TestSession en el controlador:

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

Nunca te burles … ¡nunca! La solución es bastante simple. ¿Por qué falsificar una creación tan hermosa como HttpContext ?

¡Empuja la sesión hacia abajo! (Solo esta línea es suficiente para que la mayoría de nosotros la comprenda, pero se explica en detalle a continuación)

(string)HttpContext.Current.Session["CustomerId"]; es cómo accedemos ahora. Cambiar esto a

 _customObject.SessionProperty("CustomerId") 

Cuando se llama desde la prueba, _customObject usa una tienda alternativa (DB o valor de clave en la nube [ http://www.kvstore.io/%5D )

Pero cuando se llama desde la aplicación real, _customObject usa Session .

¿Cómo se hace esto? bien … Inyección de dependencia!

Entonces, la prueba puede configurar la sesión (clandestina) y luego llamar al método de la aplicación como si no supiera nada sobre la sesión. Luego, la prueba comprueba secretamente si el código de la aplicación actualizó correctamente la sesión. O si la aplicación se comporta en función del valor de sesión establecido por la prueba.

En realidad, terminamos burlándonos a pesar de que dije: “nunca te burles”. Porque no pudimos evitar pasar a la siguiente regla, “¡fingir dónde duele menos!”. Burlarse de HttpContext o burlarse de una pequeña sesión, ¿qué duele menos? no me preguntes de dónde vienen estas reglas. Digamos el sentido común. Aquí hay una lectura interesante sobre no burlarse ya que la prueba unitaria puede matarnos

La respuesta @Ro hit me ayudó mucho, pero me faltaban las credenciales de usuario porque tuve que falsificar a un usuario para probar la unidad de autenticación. Por lo tanto, permítanme describir cómo lo resolví.

De acuerdo con esto , si agrega el método

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

y luego anexar

  HttpContext.Current.User = FakeUser("myDomain\\myUser"); 

a la última línea del método TestSetup que haya terminado, las credenciales del usuario se agregan y están listas para usarse para las pruebas de autenticación.

También noté que hay otras partes en HttpContext que podría necesitar, como el método .MapPath() . Hay un FakeHttpContext disponible, que se describe aquí y se puede instalar a través de NuGet.

Encontré la siguiente solución simple para especificar un usuario en HttpContext: https://forums.asp.net/post/5828182.aspx