Configuración de HttpContext.Actual.Sesión en una prueba unitaria


Tengo un servicio web que estoy tratando de probar unitariamente. En el servicio extrae varios valores de 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 petición worker, así:

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

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

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

Obtengo la excepción de referencia null que dice HttpContext.Current.Session es null.

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

Author: CodeNotFound, 2012-03-09

13 answers

Tuvimos que burlarnos de HttpContext usando un HttpContextManager y llamando a la fábrica desde nuestra aplicación, así como 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;
    }
}

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

Este es un ejemplo usando 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;
}

Y luego para usarlo dentro de sus pruebas unitarias, lo llamo dentro de mi método de inicio de prueba

HttpContextManager.SetCurrentContext(GetMockedHttpContext());

Usted puede entonces, en el método anterior, agregue los resultados esperados de la sesión que espera que esté disponible para su servicio web.

 95
Author: Anthony Shaw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-27 08:46:10

Puedes "fingirlo" creando un nuevo HttpContext como este:

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

He tomado ese código y lo he puesto en una clase auxiliar 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 reflexión para construir la nueva instancia HttpSessionState, puedes adjuntar tu HttpSessionStateContainer a la HttpContext (según el comentario de Brent M. Spell):

SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);

Y luego puedes llamarlo en tus pruebas unitarias como:

HttpContext.Current = MockHelper.FakeHttpContext();
 261
Author: Milox,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-27 08:46:53

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

He hecho algunos cambios para que funcione correctamente con cualquier 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;
}
 39
Author: giammin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 10:31:28

Hace un tiempo que quiero hacer algo al respecto.

Pruebas unitarias HttpContext.Actual.Sesión en MVC3. NET

Espero que 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);
}
 30
Author: Ro Hit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-31 05:48:00

Si está utilizando el framework MVC, esto debería funcionar. Usé el FakeHttpContext de Milox y agregué algunas líneas adicionales de código. La idea surgió de este post:

Http://codepaste.net/p269t8

Esto parece funcionar en MVC 5. No he probado esto 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();
 11
Author: Nimblejoe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:13

Puedes probar FakeHttpContext :

using (new FakeHttpContext())
{
   HttpContext.Current.Session["CustomerId"] = "customer1";       
}
 10
Author: vAD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-27 10:41:03

La respuesta que funcionó conmigo es lo que @Anthony había escrito, pero hay que añadir otra línea que es

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

Así que puedes usar esto:

HttpContextFactory.Current.Request.Headers.Add(key, value);
 7
Author: yzicus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-24 12:59:49

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 fue

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

Considere usar Moq

new Mock<ISession>();
 5
Author: KCD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:05

Prueba esto:

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

Y Añadir la clase:

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

Esto le permitirá probar tanto con la sesión como con la caché.

 1
Author: Isaac Alvarado,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-03 20:07:25

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

Luego agregué un parámetro opcional al constructor de mi controlador. Si el parámetro está presente, utilícelo para la manipulación de la sesión. De lo contrario, utilice HttpContext.Sesión:

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);
    }
}
 1
Author: Chris Hanson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-24 19:55:30

Nunca simulacro.. nunca! La solución es bastante simple. ¿Por qué fingir una creación tan hermosa como HttpContext?

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

(string)HttpContext.Current.Session["CustomerId"]; es como accedemos ahora. Cambie esto a

_customObject.SessionProperty("CustomerId")

Cuando se llama desde test, _customObject usa un almacén alternativo (DB o cloud key value [http://www.kvstore.io/] )

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

¿Cómo se hace esto? bien... Inyección de Dependencia!

Así que test puede establecer la sesión(underground) y luego llamar al método de aplicación como si no supiera nada sobre la sesión. Luego test 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 deslizarnos a la siguiente regla, " burlarnos donde duele menos!". Burlarse de enorme HttpContext o burlarse de una pequeña sesión, que duele menos? no me preguntes de dónde vienen estas reglas. Digamos sentido común. Aquí hay una lectura interesante sobre no burlarse ya que la prueba unitaria puede matarnos

 1
Author: Blue Clouds,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-11 05:17:20

La respuesta @Ro Hit gave me ayudó mucho, pero me faltaban las credenciales de usuario porque tuve que falsificar un usuario para realizar pruebas unitarias de autenticación. Por lo tanto, permítanme describir cómo lo resolví.

De acuerdo con este , si se 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 añadir

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

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

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

 0
Author: Matt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:00

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

 0
Author: Lars Ladegaard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-21 13:14:28