Установка HttpContext.Current.Session в модульном тесте

У меня есть веб-служба, которую я пытаюсь выполнить для модульного тестирования. В сервисе он извлекает несколько значений из 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 равно нулю.

Есть ли способ инициализировать текущий сеанс в модульном тесте?


person DaveB    schedule 08.03.2012    source источник
comment
Вы пробовали этот метод?   -  person Raj Ranjhan    schedule 09.03.2012
comment
По возможности используйте HttpContextBase.   -  person jrummell    schedule 09.03.2012


Ответы (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 и поиздеваться над своими ожиданиями.

Это пример использования 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;
}

а затем, чтобы использовать его в своих модульных тестах, я вызываю его в своем методе Test Init

HttpContextManager.SetCurrentContext(GetMockedHttpContext());

Затем вы можете в указанном выше методе добавить ожидаемые результаты сеанса, которые, как вы ожидаете, будут доступны для вашей веб-службы.

person Anthony Shaw    schedule 08.03.2012
comment
но это не использует SimpleWorkerRequest - person knocte; 28.08.2012
comment
он пытался издеваться над HttpContext, чтобы его SimpleWorkerRequest имел доступ к значениям из HttpContext, он использовал HttpContextFactory в своей службе - person Anthony Shaw; 28.08.2012
comment
Умышленно ли, что поддерживающее поле m_context возвращается только для фиктивного контекста (при установке через SetCurrentContext), а для реального HttpContext создается оболочка для каждого вызова Current? - person Stephen Price; 01.07.2013
comment
Да, это. m_context имеет тип HttpContextBase, и возвращающий HttpContextWrapper возвращает HttpContextBase с текущим HttpContext. - person Anthony Shaw; 01.07.2013
comment
Я думаю, что класс следует называть не фабрикой, а чем-то еще, может быть HttpContextSource, потому что он не создает новых объектов. - person user1713059; 08.05.2015
comment
HttpContextManager было бы лучше, чем HttpContextSource, но я согласен, что HttpContextFactory вводит в заблуждение. - person Professor of programming; 18.09.2015
comment
@AnthonyShaw: Я реализовал ваш код, но все же он дает HttpContext.current null в действии Mvc. - person Amit Kumar; 21.10.2015

Вы можете "подделать это", создав новый HttpContext следующим образом:

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

Я взял этот код и поместил его в статический вспомогательный класс следующим образом:

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

Или вместо использования отражения для создания нового экземпляра HttpSessionState вы можете просто прикрепить свой HttpSessionStateContainer к HttpContext (согласно комментарию Брента М. Спелла):

SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);

а затем вы можете вызвать его в своих модульных тестах, например:

HttpContext.Current = MockHelper.FakeHttpContext();
person Milox    schedule 12.04.2012
comment
Мне этот ответ нравится больше, чем принятый, потому что изменение производственного кода для поддержки ваших действий по тестированию - плохая практика. Конечно, ваш производственный код должен абстрагироваться от сторонних пространств имен, подобных этому, но когда вы работаете с устаревшим кодом, у вас не всегда есть этот элемент управления или роскошь для повторного факторизации. - person Sean Glover; 23.08.2012
comment
Вам не нужно использовать отражение для создания нового экземпляра HttpSessionState. Вы можете просто прикрепить свой HttpSessionStateContainer к HttpContext с помощью SessionStateUtility.AddHttpSessionStateToContext. - person Brent M. Spell; 26.09.2012
comment
MockHelper - это просто имя класса, в котором находится статический метод, вы можете использовать любое имя, которое захотите. - person Milox; 17.08.2013
comment
Я пробовал реализовать ваш ответ, но сеанс все еще нулевой. Не могли бы вы взглянуть на мой пост stackoverflow.com/questions/23586765/. Спасибо - person Joe; 11.05.2014
comment
Server.MapPath() не будет работать, если вы воспользуетесь этим. - person Yuck; 22.12.2014
comment
Есть идеи, как писать на HttpRequest.InputStream, используя этот подход? - person Steven de Salas; 12.03.2015
comment
Этот ответ работал после того, как принятый ответ не работал. Спасибо :) - person Omar.Ebrahim; 28.01.2016
comment
Я получаю пустой HttpContext.Current.Request.ApplicationPath, возможно, поэтому Server.MapPath() не работает @Yuck? Есть идеи, как его установить? - person drzaus; 14.07.2016
comment
Я пытался использовать FakeHttpContext, но мне пришлось отказаться от него, потому что он не может устанавливать заголовки запроса. Поэтому мне пришлось вместо этого использовать HttpContextManager. - person John Henckel; 03.08.2016
comment
это довольно давно, но в некоторых кодах я использую UrlHelper uHelper = new UrlHelper (_context.Request.RequestContext); , а затем я использую uHelper.Content (...) на некоторых URL-адресах. Это дает мне нулевое исключение, когда я использую ваш пример для тестов. Как я могу отредактировать это, чтобы urlhelper, созданный из этого контекста, работал? - person Phil; 04.04.2019

Решение Milox лучше, чем принятое. IMHO, но У меня были некоторые проблемы с этой реализацией при обработке URL-адресов с запросом.

Я внес некоторые изменения, чтобы он работал правильно с любыми 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;
}
person giammin    schedule 31.10.2013
comment
Это позволяет вам подделывать httpContext.Session, есть идеи, как сделать то же самое для httpContext.Application? - person KyleMit; 19.08.2016

Некоторое время назад я что-то беспокоился об этом.

Модульное тестирование HttpContext.Current.Session в 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);
}
person Ro Hit    schedule 31.10.2013
comment
Работает так прекрасно и просто ... Спасибо! - person mggSoft; 16.07.2018

Если вы используете платформу MVC, это должно сработать. Я использовал Milox FakeHttpContext и добавил несколько дополнительные строки кода. Идея пришла из этого поста:

http://codepaste.net/p269t8

Кажется, это работает в MVC 5. Я не пробовал этого в более ранних версиях 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();
person Nimblejoe    schedule 01.11.2014
comment
Ссылка не работает, так что, возможно, в следующий раз поместите сюда код. - person Rhyous; 09.03.2018

Вы можете попробовать FakeHttpContext:

using (new FakeHttpContext())
{
   HttpContext.Current.Session["CustomerId"] = "customer1";       
}
person vAD    schedule 27.07.2015
comment
Отлично работает и очень прост в использовании - person Beanwah; 14.09.2016
comment
К сожалению, несовместим с .NET Core - person Luis Gouveia; 22.03.2021
comment
@LuisGouveia, есть ли у .NET Core вообще такая проблема? - person vAD; 23.03.2021

В 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>();
person KCD    schedule 18.05.2016

Попробуй это:

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

Это позволит вам тестировать как сеанс, так и кеш.

person Isaac Alvarado    schedule 03.02.2016

Я искал что-то менее агрессивное, чем упомянутые выше варианты. В конце концов, я придумал дрянное решение, но оно могло заставить некоторых людей двигаться немного быстрее.

Сначала я создал класс 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.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;
    }
}

Теперь я могу ввести свой TestSession в контроллер:

class MyControllerTest
{

    private readonly MyController _controller;

    public MyControllerTest()
    {
        var testSession = new TestSession();
        var _controller = new MyController(testSession);
    }
}
person Chris Hanson    schedule 24.01.2017
comment
Мне очень нравится твое решение. KISS = ›Будь простым и глупым ;-) - person CodeNotFound; 17.08.2017

Никогда не смейся .. никогда! Решение довольно простое. Зачем подделывать такое красивое творение, как HttpContext?

Отодвиньте сеанс! (Для большинства из нас достаточно этой строки, но она подробно описана ниже)

(string)HttpContext.Current.Session["CustomerId"]; - вот как мы получаем к нему доступ сейчас. Измените это на

_customObject.SessionProperty("CustomerId")

При вызове из теста _customObject использует альтернативное хранилище (значение ключа базы данных или облака [http://www.kvstore.io/]])

Но при вызове из реального приложения _customObject использует Session.

как это делается? ну ... инъекция зависимостей!

Таким образом, test может установить сеанс (под землей), а затем вызвать метод приложения, как будто он ничего не знает о сеансе. Затем test тайно проверяет, правильно ли код приложения обновил сеанс. Или, если приложение ведет себя на основе значения сеанса, установленного тестом.

На самом деле, мы закончили тем, что издевались, хотя я сказал: «Никогда не смейся». Потому что мы не могли не перейти к следующему правилу: «Шути, где меньше всего больно!». Издеваться над огромным HttpContext или высмеивать крошечный сеанс, что меньше всего вредит? не спрашивайте меня, откуда взялись эти правила. Скажем так, здравый смысл. Вот интересное чтение о том, как не издеваться над поскольку модульный тест может нас убить

person Blue Clouds    schedule 11.04.2018

Ответ @Ro Hit очень помог мне, но мне не хватало учетных данных пользователя, потому что Мне пришлось подделать пользователя для модульного тестирования аутентификации. Поэтому позвольте мне описать, как я это решил.

Согласно this, если вы добавите метод

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

person Matt    schedule 08.07.2016

Я нашел следующее простое решение для указания пользователя в HttpContext: https://forums.asp.net/post/5828182.aspx

person Lars Ladegaard    schedule 21.02.2018

Попробуйте так ..

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;
  }
person Ranjan Singh    schedule 03.07.2019