web-dev-qa-db-fra.com

Définition de HttpContext.Current.Session dans un test unitaire

J'ai un service Web que j'essaye de tester. Dans le service, il tire plusieurs valeurs de la HttpContext comme ceci:

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

dans le test unitaire, je crée le contexte à l'aide d'une simple requête de travailleur, comme ceci:

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

Cependant, chaque fois que j'essaie de définir les valeurs de HttpContext.Current.Session

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

J'obtiens une exception de référence nulle qui dit que HttpContext.Current.Session est nul. 

Est-il possible d'initialiser la session en cours dans le test unitaire?

164
DaveB

Nous avons dû nous moquer de HttpContext en utilisant HttpContextManager et en appelant l’usine à partir de notre application ainsi que des tests unitaires

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

Vous pouvez alors remplacer tous les appels à HttpContext.Current par HttpContextManager.Current et avoir accès aux mêmes méthodes. Ensuite, lorsque vous testez, vous pouvez également accéder à la HttpContextManager et vous moquer de vos attentes.

Ceci est un exemple utilisant 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;
}

et ensuite pour l'utiliser dans vos tests unitaires, j'appelle cela dans ma méthode Test Init

HttpContextManager.SetCurrentContext(GetMockedHttpContext());

vous pouvez ensuite, dans la méthode ci-dessus, ajouter les résultats attendus de la session que vous prévoyez être disponibles pour votre service Web.

95
Anthony Shaw

Vous pouvez "simuler" en créant une nouvelle variable HttpContext comme ceci:

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

J'ai pris ce code et l'ai mis sur une classe d'assistance statique comme ceci: 

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

Ou au lieu d'utiliser la réflexion pour construire la nouvelle instance HttpSessionState, vous pouvez simplement attacher votre HttpSessionStateContainer à la HttpContext (selon le commentaire de Brent M. Spell):

SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);

et alors vous pouvez l'appeler dans vos tests unitaires comme:

HttpContext.Current = MockHelper.FakeHttpContext();
268
Milox

La solution Milox est meilleure que celle qui est acceptée à mon humble avis mais J'ai eu quelques problèmes avec cette implémentation lors de la manipulation des URL avec querystring .

J'ai apporté quelques modifications pour que cela fonctionne correctement avec toutes les URL et pour éviter les réflexions.

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
giammin

J'ai quelque chose à ce sujet il y a un moment. 

Test de l'unité HttpContext.Current.Session dans MVC3 .NET

J'espère que ça aide.

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

Si vous utilisez le framework MVC, cela devrait fonctionner. J'ai utilisé Milox's FakeHttpContext et ajouté quelques lignes de code supplémentaires. L'idée est venue de ce post:

http://codepaste.net/p269t8

Cela semble fonctionner dans MVC 5. Je n'ai pas essayé cela dans les versions précédentes 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();
12
Nimblejoe

Vous pouvez essayer FakeHttpContext :

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

La réponse qui a fonctionné avec moi est ce que @ Anthony a écrit, mais vous devez ajouter une autre 

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

afin que vous puissiez utiliser ceci:

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

Dans asp.net Core/MVC 6 rc2, vous pouvez définir la variable HttpContext.

var SomeController controller = new SomeController();

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

rc 1 était

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

Pensez à utiliser Moq

new Mock<ISession>();
5
KCD

Jamais moqueur .. jamais! La solution est assez simple. Pourquoi simuler une si belle création telle que HttpContext

Poussez la session! (Cette ligne suffit à la plupart d’entre nous pour comprendre, mais est expliquée en détail ci-dessous)

(string)HttpContext.Current.Session["CustomerId"]; est comment nous y accédons maintenant. Changer ceci en 

_customObject.SessionProperty("CustomerId")

Lorsqu'il est appelé à partir de test, _customObject utilise un autre magasin (valeur de clé de base de données ou de nuage [ http://www.kvstore.io/] )

Mais lorsqu'il est appelé depuis l'application réelle, _customObject utilise Session.

comment est-ce fait? bien ... Injection de dépendance! 

Donc, test peut définir la session (en sous-sol), puis appeler la méthode d'application comme si elle ne connaissait rien de la session. Puis, teste secrètement si le code de l'application a correctement mis à jour la session. Ou si l'application se comporte en fonction de la valeur de session définie par le test.

En fait, nous nous sommes moqués même si j'ai dit: "ne te moque jamais". Parce que nous ne pouvions pas m'empêcher de passer à la règle suivante, "se moque là où ça fait le moins mal!". Se moquant d'énorme HttpContext ou se moquant d'une petite session, qui fait le moins mal? ne me demandez pas d'où viennent ces règles. Disons simplement le bon sens. Voici une lecture intéressante sur ne pas se moquer car le test unitaire peut nous tuer

1
Blue Clouds

Essaye ça: 

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

Et ajoutez la classe:

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

Cela vous permettra de tester avec session et cache.

1
Isaac Alvarado

Je cherchais quelque chose d'un peu moins invasif que les options mentionnées ci-dessus. À la fin, j’ai proposé une solution de mauvais goût, mais certaines personnes pourraient agir un peu plus vite.

J'ai d'abord créé un TestSession class:

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

Ensuite, j'ai ajouté un paramètre facultatif au constructeur de mon contrôleur. Si le paramètre est présent, utilisez-le pour la manipulation de session. Sinon, utilisez le 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;
    }
}

Maintenant je peux injecter mon TestSession dans le contrôleur:

class MyControllerTest
{

    private readonly MyController _controller;

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

La réponse @Ro Hit m'a beaucoup aidé, mais les informations d'identification de l'utilisateur me manquaient car je devais simuler un utilisateur pour les tests d'unité d'authentification. Par conséquent, permettez-moi de décrire comment je l'ai résolu.

Selon ceci , si vous ajoutez la méthode

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

et ensuite ajouter

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

À la dernière ligne de la méthode TestSetup que vous avez terminée, les informations d'identification de l'utilisateur sont ajoutées et prêtes à être utilisées pour le test d'authentification.

J'ai également remarqué qu'il y a d'autres parties de HttpContext dont vous pourriez avoir besoin, telles que la méthode .MapPath(). Il existe un FakeHttpContext disponible, qui est décrit ici et peut être installé via NuGet.

0
Matt

Essayez de cette façon ..

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;
  }
0
Ranjan Singh

J'ai trouvé la solution simple suivante pour spécifier un utilisateur dans HttpContext: https://forums.asp.net/post/5828182.aspx

0
Lars Ladegaard