Установка HttpContext.Current.Session в unit test

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

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

Ответ 1

Нам пришлось издеваться над HttpContext, используя HttpContextManager и вызывая фабрику из нашего приложения, а также из 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());

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

Ответ 2

Вы можете "подделать это", создав новый 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://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):

SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);

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

HttpContext.Current = MockHelper.FakeHttpContext();

Ответ 3

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

Я сделал некоторые изменения, чтобы заставить его работать с любыми URL-адресами и избежать 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;
}

Ответ 4

Я немного что-то об этом говорил.

Тестирование модулей 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);
}

Ответ 5

Если вы используете среду MVC, это должно работать. Я использовал Milox's 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();

Ответ 6

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

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

Ответ 7

Ответ, который работал со мной, это то, что написал @Anthony, но вам нужно добавить еще одну строку, которая

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

чтобы вы могли использовать это:

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

Ответ 8

В 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();

fooobar.com/questions/42486/...

Рассмотрим использование Moq

new Mock<ISession>();

Ответ 9

Попробуйте следующее:

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

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

Ответ 10

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

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

Ответ 11

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

Нажмите сеанс вниз! (Этого достаточно для понимания большинства из нас, но подробно объяснено ниже)

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

_customObject.SessionProperty("CustomerId")

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

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

как это сделать? хорошо... инъекция зависимости!

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

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

Ответ 12

Ответ @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.

Ответ 14

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

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