Festlegen von HttpContext.Current.Session in einem Komponententest


185

Ich habe einen Webdienst, den ich einem Unit-Test unterziehen möchte. Im Dienst werden mehrere Werte aus dem HttpContextGleichen abgerufen, so:

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

Im Unit-Test erstelle ich den Kontext mithilfe einer einfachen Worker-Anfrage wie folgt:

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

Wenn ich jedoch versuche, die Werte von einzustellen HttpContext.Current.Session

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

Ich erhalte eine Nullreferenzausnahme, die besagt, dass sie HttpContext.Current.Sessionnull ist.

Gibt es eine Möglichkeit, die aktuelle Sitzung innerhalb des Komponententests zu initialisieren?


Haben Sie diese Methode ausprobiert ?
Raj Ranjhan

Verwenden Sie HttpContextBase, wenn Sie können.
Jrummell

Antworten:


105

Wir mussten uns verspotten, HttpContextindem wir a verwendeten HttpContextManagerund die Fabrik aus unserer Anwendung heraus sowie die Unit-Tests anriefen

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

Sie würden dann alle Aufrufe von HttpContext.Currentdurch ersetzen HttpContextManager.Currentund Zugriff auf dieselben Methoden haben. Wenn Sie dann testen, können Sie auch auf die zugreifen HttpContextManagerund Ihre Erwartungen verspotten

Dies ist ein Beispiel mit 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;
}

und um es dann in Ihren Unit-Tests zu verwenden, rufe ich dies in meiner Test Init-Methode auf

HttpContextManager.SetCurrentContext(GetMockedHttpContext());

Sie können dann in der obigen Methode die erwarteten Ergebnisse aus der Sitzung hinzufügen, von denen Sie erwarten, dass sie für Ihren Webdienst verfügbar sind.


1
aber dies verwendet nicht SimpleWorkerRequest
knocte

Er versuchte, den HttpContext zu verspotten, damit seine SimpleWorkerRequest Zugriff auf die Werte aus dem HttpContext hatte. Er verwendete die HttpContextFactory in seinem Dienst
Anthony Shaw

Ist beabsichtigt, dass das Hintergrundfeld m_context nur für einen Scheinkontext zurückgegeben wird (wenn es über SetCurrentContext festgelegt wird) und dass für den echten HttpContext für jeden Aufruf von Current ein Wrapper erstellt wird?
Stephen Price

Ja, so ist es. m_context ist vom Typ HttpContextBase und die Rückgabe von HttpContextWrapper gibt HttpContextBase mit dem aktuellen HttpContext zurück
Anthony Shaw

1
HttpContextManagerwäre ein besserer Name als, HttpContextSourceaber ich stimme zu, HttpContextFactoryist irreführend.
Professor für Programmierung

298

Sie können es "fälschen", indem Sie ein neues erstellen HttpContext wie :

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

Ich habe diesen Code genommen und ihn wie folgt in eine statische Hilfsklasse eingefügt:

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

Oder statt Reflexion mit dem neuen bauen HttpSessionStateInstanz, können Sie einfach Ihre anhängen HttpSessionStateContainerauf die HttpContext(per Brent M. Spell Kommentar):

SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);

und dann können Sie es in Ihren Unit-Tests wie folgt aufrufen:

HttpContext.Current = MockHelper.FakeHttpContext();

24
Diese Antwort gefällt mir besser als die akzeptierte, da das Ändern Ihres Produktionscodes zur Unterstützung Ihrer Testaktivitäten eine schlechte Praxis ist. Zugegeben, Ihr Produktionscode sollte Namespaces von Drittanbietern wie diesen abstrahieren, aber wenn Sie mit Legacy-Code arbeiten, haben Sie nicht immer diese Kontrolle oder den Luxus, sie neu zu faktorisieren.
Sean Glover

29
Sie müssen keine Reflektion verwenden, um die neue HttpSessionState-Instanz zu erstellen. Sie können Ihren HttpSessionStateContainer einfach mit SessionStateUtility.AddHttpSessionStateToContext an den HttpContext anhängen.
Brent M. Spell

MockHelper ist nur der Name der Klasse, in der sich die statische Methode befindet. Sie können einen beliebigen Namen verwenden.
Milox

Ich habe versucht, Ihre Antwort zu implementieren, aber die Sitzung ist immer noch null. Bitte werfen Sie einen Blick auf meinen Beitrag stackoverflow.com/questions/23586765/… . Vielen Dank
Joe

Server.MapPath()funktioniert auch nicht, wenn Sie dies verwenden.
Yuck

45

Die Milox-Lösung ist meiner Meinung nach besser als die akzeptierte, aber ich hatte einige Probleme mit dieser Implementierung beim Umgang mit URLs mit Querystring .

Ich habe einige Änderungen vorgenommen, damit es mit allen URLs richtig funktioniert und Reflection vermieden wird.

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

Dies ermöglicht es Ihnen httpContext.Session, eine Idee zu fälschen , wie Sie dasselbe tun können httpContext.Application?
KyleMit

39

Ich habe vor einiger Zeit etwas darüber gesagt.

Unit-Test HttpContext.Current.Session in MVC3 .NET

Ich hoffe es hilft.

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

Funktioniert so gut und einfach ... Danke!
mggSoft

12

Wenn Sie das MVC-Framework verwenden, sollte dies funktionieren. Ich habe Milox ' FakeHttpContext verwendet und ein paar zusätzliche Codezeilen hinzugefügt. Die Idee kam von diesem Beitrag:

http://codepaste.net/p269t8

Dies scheint in MVC 5 zu funktionieren. Ich habe dies in früheren Versionen von MVC nicht versucht.

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

3
Der Link ist defekt, also geben Sie den Code vielleicht beim nächsten Mal hier ein.
Rhyous

11

Sie können FakeHttpContext ausprobieren :

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

Funktioniert großartig und sehr einfach zu bedienen
Beanwah

7

Die Antwort, die bei mir funktioniert hat, ist die, die @Anthony geschrieben hat, aber Sie müssen eine weitere Zeile hinzufügen

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

So können Sie Folgendes verwenden:

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

7

In asp.net Core / MVC 6 rc2 können Sie die einstellen HttpContext

var SomeController controller = new SomeController();

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

rc 1 war

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

Erwägen Sie die Verwendung Moq

new Mock<ISession>();

2

Versuche dies:

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

Und füge die Klasse hinzu:

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

Auf diese Weise können Sie sowohl mit der Sitzung als auch mit dem Cache testen.


1

Ich suchte nach etwas weniger Invasivem als den oben genannten Optionen. Am Ende habe ich eine kitschige Lösung gefunden, aber es könnte einige Leute dazu bringen, sich etwas schneller zu bewegen.

Zuerst habe ich eine TestSession- Klasse erstellt:

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

Dann habe ich dem Konstruktor meines Controllers einen optionalen Parameter hinzugefügt. Wenn der Parameter vorhanden ist, verwenden Sie ihn zur Sitzungsmanipulation. Verwenden Sie andernfalls die Datei 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;
    }
}

Jetzt kann ich meine TestSession in den Controller einspeisen :

class MyControllerTest
{

    private readonly MyController _controller;

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

Ihre Lösung gefällt mir sehr gut. KISS => Halte es einfach und dumm ;-)
CodeNotFound

1

Niemals verspotten ... niemals! Die Lösung ist ziemlich einfach. Warum so eine schöne Kreation vortäuschen?HttpContext ?

Drücken Sie die Sitzung nach unten! (Nur diese Zeile reicht für die meisten von uns aus, um sie zu verstehen, wird aber unten ausführlich erklärt.)

(string)HttpContext.Current.Session["CustomerId"];So greifen wir jetzt darauf zu. Ändern Sie dies in

_customObject.SessionProperty("CustomerId")

Beim Aufruf von test verwendet _customObject einen alternativen Speicher (DB- oder Cloud-Schlüsselwert [ http://www.kvstore.io/]). ).

Aber wenn von der realen Anwendung aufgerufen, _customObjectverwendetSession .

Wie wird das gemacht? gut ... Abhängigkeitsinjektion!

Test kann also die Sitzung (unterirdisch) einstellen und dann die Anwendungsmethode aufrufen, als ob sie nichts über die Sitzung weiß. Testen Sie dann heimlich, ob der Anwendungscode die Sitzung korrekt aktualisiert hat. Oder wenn sich die Anwendung basierend auf dem vom Test festgelegten Sitzungswert verhält.

Eigentlich haben wir uns verspottet, obwohl ich gesagt habe: "Niemals verspotten". Weil wir nicht anders konnten, als zur nächsten Regel überzugehen: "Verspotten, wo es am wenigsten weh tut!". Verspotten Sie sich riesig HttpContextoder verspotten Sie eine winzige Sitzung, was am wenigsten schmerzt? Frag mich nicht, woher diese Regeln kommen. Sagen wir einfach gesunden Menschenverstand. Hier ist eine interessante Lektüre über das Nicht-Verspotten, da Unit-Tests uns töten können


0

Die Antwort @Ro Hit gab, hat mir sehr geholfen, aber mir fehlten die Benutzeranmeldeinformationen, weil ich einen Benutzer für das Testen der Authentifizierungseinheit vortäuschen musste. Lassen Sie mich daher beschreiben, wie ich es gelöst habe.

Nach diesem , wenn Sie die Methode hinzufügen

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

und dann anhängen

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

bis zur letzten Zeile der TestSetup Methode, die Sie abgeschlossen haben, werden die Benutzeranmeldeinformationen hinzugefügt und können für Authentifizierungstests verwendet werden.

Mir ist auch aufgefallen, dass in HttpContext möglicherweise andere Teile erforderlich sind, z. B. die .MapPath()Methode. Es steht ein FakeHttpContext zur Verfügung, der hier beschrieben wird und über NuGet installiert werden kann.



0

Versuchen Sie diesen Weg ..

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;
  }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.