Impostazione di HttpContext.Current.Session in un test di unità

Ho un servizio web che sto cercando di testare unitamente. Nel servizio prende diversi valori da HttpContext modo:

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

nel test unitario 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 che HttpContext.Current.Session è null.

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

Abbiamo dovuto prendere in giro HttpContext utilizzando un HttpContextManager e 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; } } 

Dovresti quindi sostituire qualsiasi chiamata a HttpContext.Current con HttpContextManager.Current e avere accesso agli stessi metodi. Poi, quando stai provando, puoi anche accedere a HttpContextManager e HttpContextManager giro le tue aspettative

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

e quindi usarlo nei test di unità, lo chiamo nel mio metodo Test Init

 HttpContextManager.SetCurrentContext(GetMockedHttpContext()); 

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

Puoi “fingere” creando un nuovo HttpContext come questo:

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

Ho preso quel codice e lo metto su una class helper statica in questo modo:

 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 invece di usare reflection per build la nuova istanza di HttpSessionState , puoi semplicemente colbind HttpSessionStateContainer a HttpContext (come da commento di Brent M. Spell):

 SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer); 

e quindi puoi chiamarlo nelle tue unit test come:

 HttpContext.Current = MockHelper.FakeHttpContext(); 

La soluzione Milox è migliore di quella accettata, ma ho avuto alcuni problemi con questa implementazione quando gestivo gli url con querystring .

Ho apportato alcune modifiche per farlo funzionare correttamente con qualsiasi URL e per 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; } 

Mi sono fatto un po ‘di 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); } 

Se stai usando il framework MVC, questo dovrebbe funzionare. Ho usato Milox’s FakeHttpContext e ho aggiunto alcune righe aggiuntive di codice. L’idea è venuta 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(); 

Puoi provare FakeHttpContext :

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

La risposta che ha funzionato con me è ciò che @Anthony ha scritto, ma devi aggiungere un’altra linea che è

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

quindi puoi usare questo:

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

In asp.net Core / MVC 6 rc2 puoi 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’uso di Moq

 new Mock(); 

Prova questo:

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

E aggiungi la class:

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

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

Stavo cercando qualcosa di un po ‘meno invasivo rispetto alle opzioni di cui sopra. Alla fine ho trovato una soluzione scadente, ma potrebbe far muovere alcune persone un po ‘più velocemente.

Per prima cosa ho creato una class 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; } } 

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

Mai prendere in giro .. mai! La soluzione è piuttosto semplice. Perché fingere una creazione così bella come HttpContext ?

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

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

 _customObject.SessionProperty("CustomerId") 

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

Ma quando chiamato dall’applicazione reale, _customObject utilizza Session .

come è fatto? bene … Iniezione delle dipendenze!

Quindi test può impostare la sessione (underground) e quindi chiamare il metodo dell’applicazione come se non sapesse nulla sulla sessione. Quindi verifica segretamente se il codice dell’applicazione ha aggiornato correttamente la sessione. Oppure se l’applicazione si comporta in base al valore di sessione impostato dal test.

In realtà, abbiamo finito per deridere anche se ho detto: “non prendere mai in giro”. Siccome non potremmo fare a meno di passare alla regola successiva, “prendi in giro dove fa meno male!”. Scherzando HttpContext enorme o HttpContext giro una sessione minuscola, che fa male il meno? non chiedermi da dove vengono queste regole. Diciamo solo buonsenso. Ecco una lettura interessante su non deridere perché il test unitario può ucciderci

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

In base a ciò , 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 poi aggiungere

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

fino all’ultima riga del metodo TestSetup , 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 metodo .MapPath() . È disponibile un FakeHttpContext, che è descritto qui e può essere installato tramite NuGet.

Ho trovato la seguente semplice soluzione per specificare un utente in HttpContext: https://forums.asp.net/post/5828182.aspx