Impostazione di HttpContext.Current.Session in un unit test


185

Ho un servizio web che sto cercando di testare l'unità. Nel servizio estrae diversi valori dal HttpContextsimile così:

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

nel unit test sto creando il contesto usando una semplice richiesta di lavoro, in questo modo:

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

Tuttavia, ogni volta che provo a impostare i valori di HttpContext.Current.Session

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

Ottengo un'eccezione di riferimento null che dice HttpContext.Current.Sessionnull.

C'è un modo per inizializzare la sessione corrente all'interno del test unitario?


Hai provato questo metodo ?
Raj Ranjhan,

Usa HttpContextBase se puoi.
Jrummell,

Risposte:


105

Abbiamo dovuto prendere in giro HttpContextusando un HttpContextManagere chiamando la fabbrica dalla nostra applicazione, nonché i Test delle unità

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

Sostituiresti quindi tutte le chiamate HttpContext.Currentcon HttpContextManager.Currente avresti accesso agli stessi metodi. Quindi, durante i test, puoi anche accedere HttpContextManagere deridere le tue aspettative

Questo è un esempio 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;
}

e poi per usarlo nei test unitari, lo chiamo nel mio metodo Test Init

HttpContextManager.SetCurrentContext(GetMockedHttpContext());

puoi quindi, con il metodo sopra, aggiungere i risultati attesi da Sessione che ti aspetti siano disponibili per il tuo servizio web.


1
ma questo non usa SimpleWorkerRequest
knocte il

stava cercando di deridere HttpContext in modo che il suo SimpleWorkerRequest potesse accedere ai valori di HttpContext, avrebbe usato HttpContextFactory al suo servizio
Anthony Shaw,

È intenzionale che il campo di supporto m_context venga restituito solo per un contesto fittizio (quando impostato tramite SetCurrentContext) e che per il vero HttpContext, venga creato un wrapper per ogni chiamata a Current?
Stephen Price,

Sì. m_context è di tipo HttpContextBase e la restituzione di HttpContextWrapper restituisce HttpContextBase con l'attuale HttpContext
Anthony Shaw,

1
HttpContextManagersarebbe un nome migliore di HttpContextSourcema sono d'accordo HttpContextFactoryè fuorviante.
Professore di programmazione il

298

Puoi "fingere" creando un nuovo HttpContextcome questo:

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

Ho preso quel codice e l'ho inserito in una classe helper statica in questo modo:

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

O invece di utilizzare la riflessione per costruire la nuova HttpSessionStateistanza, si può semplicemente collegare il vostro HttpSessionStateContaineral HttpContext(secondo il commento di Brent M. Spell):

SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);

e quindi puoi chiamarlo nei tuoi test unitari come:

HttpContext.Current = MockHelper.FakeHttpContext();

24
Mi piace questa risposta meglio di quella accettata perché cambiare il tuo codice di produzione per supportare le tue attività di test è una cattiva pratica. Certo, il tuo codice di produzione dovrebbe astrarre spazi dei nomi di terze parti come questo, ma quando lavori con un codice legacy non hai sempre questo controllo o il lusso di ripensare.
Sean Glover,

29
Non è necessario utilizzare la riflessione per costruire la nuova istanza di HttpSessionState. Puoi semplicemente collegare il tuo HttpSessionStateContainer a HttpContext usando SessionStateUtility.AddHttpSessionStateToContext.
Brent M. Spell,

MockHelper è solo il nome della classe in cui si trova il metodo statico, puoi usare il nome che preferisci.
Milox

Ho provato a implementare la tua risposta ma la sessione è ancora nulla. Per favore, dai un'occhiata al mio messaggio stackoverflow.com/questions/23586765/… . Grazie
Joe,

Server.MapPath()non funzionerà se lo usi anche tu.
Yuck,

45

La soluzione Milox è migliore di quella IMHO accettata, ma ho avuto alcuni problemi con questa implementazione durante la gestione degli URL con querystring .

Ho apportato alcune modifiche per farlo funzionare correttamente con qualsiasi URL ed evitare Reflection.

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

Questo ti permette di fingere httpContext.Session, hai idea di come fare lo stesso per httpContext.Application?
KyleMit,

39

Ho scritto qualcosa a riguardo qualche tempo fa.

Test unitario HttpContext.Current.Session in MVC3 .NET

Spero che sia d'aiuto.

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

Funziona così bene e semplice ... Grazie!
mggSoft,

12

Se stai usando il framework MVC, questo dovrebbe funzionare. Ho usato FakeHttpContext di Milox e ho aggiunto alcune righe di codice aggiuntive. L'idea è nata da questo post:

http://codepaste.net/p269t8

Questo sembra funzionare in MVC 5. Non l'ho provato nelle versioni precedenti di 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();

3
Il collegamento è interrotto, quindi forse inserisci il codice qui la prossima volta.
Rhyous

11

Puoi provare FakeHttpContext :

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

Funziona alla grande e molto semplice da usare
Beanwah

8

In asp.net Core / MVC 6 rc2 è possibile impostare 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

Prendi in considerazione l'utilizzo Moq

new Mock<ISession>();

7

La risposta che ha funzionato con me è ciò che @Anthony aveva scritto, ma devi aggiungere un'altra riga che è

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

così puoi usare questo:

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

2

Prova questo:

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

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

Questo ti permetterà di testare sia con sessione che con cache.


1

Stavo cercando qualcosa di un po 'meno invasivo rispetto alle opzioni sopra menzionate. Alla fine ho trovato una soluzione di formaggio, ma alcune persone potrebbero muoversi un po 'più velocemente.

Innanzitutto ho creato una classe 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;
    }
}

Quindi ho aggiunto un parametro opzionale al costruttore del mio controller. Se il parametro è presente, utilizzarlo per la manipolazione della sessione. Altrimenti, utilizzare 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;
    }
}

Ora posso iniettare la mia TestSession nel controller:

class MyControllerTest
{

    private readonly MyController _controller;

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

Mi piace molto la tua soluzione. KISS => Keep It Simple and Stupid ;-)
CodeNotFound

1

Mai deridere .. mai! La soluzione è piuttosto semplice Perché falsificare una creazione così bella come HttpContext?

Spingi la sessione verso il basso! (Solo questa riga è sufficiente per la maggior parte di noi a capire ma spiegata in dettaglio di seguito)

(string)HttpContext.Current.Session["CustomerId"];è come accediamo ora. Cambia questo in

_customObject.SessionProperty("CustomerId")

Quando viene chiamato dal test, _customObject utilizza un archivio alternativo (valore chiave DB o cloud [ http://www.kvstore.io/] )

Ma quando viene chiamato dall'applicazione reale, _customObjectutilizza Session.

come viene fatto? bene ... iniezione di dipendenza!

Quindi test può impostare la sessione (sotterranea) e quindi chiamare il metodo dell'applicazione come se non sapesse nulla della sessione. Quindi il test controlla segretamente se il codice dell'applicazione ha aggiornato correttamente la sessione. O se l'applicazione si comporta in base al valore della sessione impostato dal test.

In realtà, ci siamo fatti beffe anche se ho detto: "mai deridere". Perché non potremmo fare a meno di passare alla regola successiva, "finto dove fa meno male!". Deridere enorme HttpContexto deridere una piccola sessione, che fa male di meno? non chiedermi da dove vengano queste regole. Diciamo solo buon senso. Ecco una lettura interessante su come non prendere in giro perché il test unitario può ucciderci


0

La risposta di @Ro Hit mi ha aiutato molto, ma mi mancavano le credenziali dell'utente perché dovevo falsificare un utente per il test dell'unità di autenticazione. Quindi, lasciami descrivere come l'ho risolto.

Secondo questo , se aggiungi il metodo

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

e quindi aggiungere

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

all'ultima riga del TestSetupmetodo eseguito, le credenziali dell'utente vengono aggiunte e pronte per essere utilizzate per i test di autenticazione.

Ho anche notato che ci sono altre parti in HttpContext che potresti richiedere, come il .MapPath()metodo. È disponibile un FakeHttpContext, che è descritto qui e può essere installato tramite NuGet.



0

Prova in questo modo ..

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;
  }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.