Paramétrage De HttpContext.Actuel.Session dans un test unitaire
j'ai un service web que j'essaie de tester en unité. Dans le service Il tire plusieurs valeurs du HttpContext
comme ainsi:
m_password = (string)HttpContext.Current.Session["CustomerId"];
m_userID = (string)HttpContext.Current.Session["CustomerUrl"];
dans le test de l'Unité, je crée le contexte en utilisant une simple demande de travail, 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";
je reçois référence nulle exception qui dit HttpContext.Current.Session
est null.
est il un moyen d'initialiser la session en cours au sein de l'unité de test?
13 réponses
nous avons dû simuler HttpContext
en utilisant un HttpContextManager
et en appelant l'usine de notre application ainsi que les tests de L'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;
}
}
vous remplacerez alors tous les appels à HttpContext.Current
par HttpContextManager.Current
et aurez accès aux mêmes méthodes. Ensuite, lorsque vous faites des tests, vous pouvez également accéder au HttpContextManager
et vous moquer de vos attentes
c'est un exemple qui utilise 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, je l'appelle dans ma méthode D'essai Init
HttpContextManager.SetCurrentContext(GetMockedHttpContext());
vous pouvez alors, dans la méthode ci-dessus ajouter les résultats attendus de la Session que vous attendez d'être disponible pour votre service web.
vous pouvez "faire semblant" en créant un nouveau HttpContext
comme ceci:
j'ai pris ce code et l'ai mis sur une classe d'Assistant statique comme ça:
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
HttpContext
(selon le commentaire de Brent M. Spell):
SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);
et ensuite vous pouvez l'appeler dans vos tests unitaires comme:
HttpContext.Current = MockHelper.FakeHttpContext();
Milox solution est meilleur que celui accepté IMHO mais j'ai eu quelques problèmes avec cette implémentation lors de la manipulation des urls avec querystring .
j'ai fait quelques changements pour le faire fonctionner correctement avec n'importe quelle URL et pour éviter la réflexion.
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;
}
j'en ai parlé il y a un moment.
"151940920 De Tests Unitaires, HttpContext.Actuel.Session dans MVC3. net
J'espère que ça aidera.
[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);
}
si vous utilisez le framework MVC, cela devrait fonctionner. J'ai utilisé Milox FakeHttpContext et ajouté quelques lignes de code supplémentaires. L'idée est venue de ce post:
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();
vous pouvez essayer FakeHttpContext :
using (new FakeHttpContext())
{
HttpContext.Current.Session["CustomerId"] = "customer1";
}
la réponse qui a fonctionné avec moi est ce que @Anthony avait écrit, mais vous devez ajouter une autre ligne qui est
request.SetupGet(req => req.Headers).Returns(new NameValueCollection());
vous pouvez donc utiliser ceci:
HttpContextFactory.Current.Request.Headers.Add(key, value);
In asp.net Core / MVC 6 rc2 vous pouvez définir le HttpContext
var SomeController controller = new SomeController();
controller.ControllerContext = new ControllerContext();
controller.ControllerContext.HttpContext = new DefaultHttpContext();
controller.HttpContext.Session = new DummySession();
rc 1 a été
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
envisager d'utiliser Moq
new Mock<ISession>();
essayez ceci:
// 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 ajouter 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 à la fois la session et le cache.
je cherchais quelque chose d'un peu moins invasive que les options mentionnées ci-dessus. À la fin, j'ai trouvé une solution ringarde, mais ça pourrait faire bouger les gens un peu plus vite.
j'ai d'Abord créé un TestSession classe:
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;
}
}
puis j'ai ajouté un paramètre optionnel 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);
}
}
ne vous moquez jamais.. jamais!!!! La solution est assez simple. Pourquoi simuler une si belle création comme HttpContext
?
repoussez la session! (Juste cette ligne est assez pour la plupart d'entre nous comprendre mais expliqué en détail ci-dessous)
(string)HttpContext.Current.Session["CustomerId"];
est la façon dont nous y accédons maintenant. Au lieu de
_customObject.SessionProperty("CustomerId")
lorsqu'il est appelé à partir de test, _customObject utilise la valeur de stockage alternative (DB ou clé de nuage [ http://www.kvstore.io/] )
mais quand appelé de l'application réelle, _customObject
utilise Session
.
comment faire? bien... Injection De Dépendance!
ainsi test peut définir la session(underground) et ensuite appeler la méthode d'application comme si elle ne sait rien sur la session. Puis testez secrètement vérifie si le code d'application correctement mis à jour la session. Ou si l'application se comporte en fonction de la session valeur fixée par le test.
en fait, nous avons fini par nous moquer même si j'ai dit: "Ne te moque jamais". Parce qu'on n'a pas pu s'empêcher de glisser vers la règle suivante: "moquez-vous là où ça fait le moins mal!". Se moquer de l'énorme HttpContext
ou se moquer d'une session minuscule, ce qui fait le moins mal? ne me demande pas d'où viennent ces règles. Disons simplement le bon sens. Voici une lecture intéressante sur ne pas se moquer comme le test de l'unité peut nous tuer
la réponse @Ro Hit m'a beaucoup aidé, mais je manquais les informations d'identification de l'utilisateur parce que je devais simuler un utilisateur pour le test de l'unité d'authentification. Permettez-moi donc de décrire comment je l'ai résolu.
Selon ce , 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 ajouter
HttpContext.Current.User = FakeUser("myDomain\myUser");
à la dernière ligne de la méthode TestSetup
vous êtes fait, les justificatifs d'identité de l'utilisateur sont ajoutés et prêts à être utilisés pour le test d'authentification.
j'ai aussi remarqué qu'il y a d'autres parties dans HttpContext dont vous pourriez avoir besoin, comme la méthode .MapPath()
. Il existe un context Fakehttp disponible, qui est décrit ici et peut être installé via NuGet.
j'ai trouvé la solution simple suivante pour spécifier un utilisateur dans le HttpContext: https://forums.asp.net/post/5828182.aspx