Установив Свойство HttpContext.Текущий.Сеанс в модульном тесте
у меня есть веб-сервис, который я пытаюсь провести модульный тест. В сервисе он вытаскивает несколько значений из HttpContext
вот так:
m_password = (string)HttpContext.Current.Session["CustomerId"];
m_userID = (string)HttpContext.Current.Session["CustomerUrl"];
в модульном тесте я создаю контекст с помощью простого рабочего запроса, например:
SimpleWorkerRequest request = new SimpleWorkerRequest("", "", "", null, new StringWriter());
HttpContext context = new HttpContext(request);
HttpContext.Current = context;
однако, когда я пытаюсь установить значения HttpContext.Current.Session
HttpContext.Current.Session["CustomerId"] = "customer1";
HttpContext.Current.Session["CustomerUrl"] = "customer1Url";
Я получаю пустой ссылкой исключение, которое говорит:HttpContext.Current.Session
имеет значение null.
есть ли способ инициализировать текущую сессию внутри устройства тест?
13 ответов:
мы должны были издеваться
HttpContext
С помощьюHttpContextManager
и вызов фабрики из нашего приложения, а также модульные тесты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; } }
затем вы замените любые вызовы на
HttpContext.Current
СHttpContextManager.Current
и имеют доступ к тем же методам. Затем, когда вы тестируете, вы также можете получить доступ кHttpContextManager
и высмеивать ваши ожиданияэто пример использования упаковка:
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; }
и затем использовать его в ваших модульных тестов, я называю это в моем Метод испытания инит
HttpContextManager.SetCurrentContext(GetMockedHttpContext());
затем вы можете в приведенном выше методе добавить ожидаемые результаты от сеанса, которые вы ожидаете, чтобы быть доступными для вашего веб-сервиса.
вы можете "подделать его", создав новый
HttpContext
такой:Я взял этот код и поместил его в статический вспомогательный класс следующим образом:
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; }
или вместо того, чтобы использовать отражение, чтобы построить новый
HttpSessionState
например, вы можете просто прикрепить свойHttpSessionStateContainer
доHttpContext
(согласно Brent M. Spell's комментарий):SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);
и тогда вы можете вызвать его в своих модульных тестах, таких как:
HttpContext.Current = MockHelper.FakeHttpContext();
решение Milox лучше, чем принятый ИМХО, но у меня были некоторые проблемы с этой реализацией при обработке URL-адресов с помощью querystring.
Я внес некоторые изменения, чтобы заставить его работать правильно с любыми URL-адресами и избегать отражения.
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; }
Я кое-что знаю об этом некоторое время назад.
Модульное Тестирование HttpContext.Текущий.Сеанс в MVC3 .NET
надеюсь, что это помогает.
[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); }
Если вы используете фреймворк MVC, это должно работать. Я использовал Milox-х FakeHttpContext и добавил несколько дополнительных строк кода. Идея пришла из этого поста:
Это, кажется, работает в MVC 5. Я не пробовал это в более ранних версиях аварии.
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();
вы можете попробовать FakeHttpContext:
using (new FakeHttpContext()) { HttpContext.Current.Session["CustomerId"] = "customer1"; }
ответ, который работал со мной, - это то, что написал @Anthony, но вы должны добавить еще одну строку, которая
request.SetupGet(req => req.Headers).Returns(new NameValueCollection());
Так что вы можете использовать этот:
HttpContextFactory.Current.Request.Headers.Add(key, value);
In asp.net Core / MVC 6 rc2 вы можете установить
HttpContext
var SomeController controller = new SomeController(); controller.ControllerContext = new ControllerContext(); controller.ControllerContext.HttpContext = new DefaultHttpContext(); controller.HttpContext.Session = new DummySession();
rc 1 был
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
рассмотрите возможность использования
Moq
new Mock<ISession>();
попробуйте это:
// 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
и добавить в класс:
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); } } }
Это позволит вам протестировать как сеанс, так и кэш.
Я искал что-то немного менее инвазивные, чем описанные выше варианты. В конце концов я придумал дрянное решение, но это может заставить некоторых людей двигаться немного быстрее.
сначала я создал 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; } }
затем я добавил дополнительный параметр в конструктор моего контроллера. Если параметр присутствует, используйте его для обработки сеанса. В противном случае используйте HttpContext.Сессия:
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; } }
Теперь я могу впрысните мой TestSession в контроллер:
class MyControllerTest { private readonly MyController _controller; public MyControllerTest() { var testSession = new TestSession(); var _controller = new MyController(testSession); } }
никогда не издевайтесь.. никогда! Решение довольно простое. Зачем подделывать такое красивое творение, как
HttpContext
?нажмите сеанс вниз! (Только эта строка достаточно для большинства из нас, чтобы понять, но подробно объяснил ниже)
(string)HttpContext.Current.Session["CustomerId"];
Как мы его сейчас. Измените это на_customObject.SessionProperty("CustomerId")
при вызове из теста _customObject использует альтернативное хранилище (DB или cloud key value[ http://www.kvstore.io/] )
но когда звонили из реальное приложение,
_customObject
используетSession
.как это сделать? что ж... Инъекция Зависимости!
поэтому test может установить сеанс (underground), а затем вызвать метод приложения, как будто он ничего не знает о сеансе. Затем тест тайно проверяет, правильно ли код приложения обновил сеанс. Или если приложение ведет себя на основе значения сеанса, установленного тестом.
на самом деле, мы в конечном итоге издевались, хотя я сказал: "Никогда не издевайтесь". Потому что мы не смог удержаться и перешел к следующему правилу: "смейся там, где тебе меньше всего больно!". Издеваются огромные
HttpContext
или издеваться над крошечным сеансом, который болит меньше всего? не спрашивайте меня, откуда взялись эти правила. Давайте просто скажем здравый смысл. Вот интересный перевод на не издеваясь как модульный тест может убить нас
ответ @Ro Hit дал мне много помог, но мне не хватало учетных данных пользователя, потому что мне пришлось подделать пользователя для тестирования модуля аутентификации. Поэтому позвольте мне описать, как я ее решил.
по данным этой, если вы добавите метод
// using System.Security.Principal; GenericPrincipal FakeUser(string userName) { var fakeIdentity = new GenericIdentity(userName); var principal = new GenericPrincipal(fakeIdentity, null); return principal; }
а потом добавить
HttpContext.Current.User = FakeUser("myDomain\myUser");
до последней строки
TestSetup
метод вы сделали, учетные данные пользователя добавляются и готовы к использованию для аутентификации тестирование.Я также заметил, что в HttpContext могут потребоваться другие части, такие как
.MapPath()
метод. Существует FakeHttpContext доступный, который является описано здесь и может быть установлен через NuGet.
Я нашел следующее простое решение для указания пользователя в HttpContext:https://forums.asp.net/post/5828182.aspx