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

185

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

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

DaveB
источник
Вы пробовали этот метод ?
Радж Ранджан
Используйте HttpContextBase, если можете.
jrummell

Ответы:

105

Нам пришлось издеваться 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());

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

Энтони Шоу
источник
1
но это не использует SimpleWorkerRequest
knocte
он пытался смоделировать HttpContext, чтобы его SimpleWorkerRequest имел доступ к значениям из HttpContext, он использовал HttpContextFactory в своем сервисе
Энтони Шоу,
Является ли намеренным, что вспомогательное поле m_context возвращается только для фиктивного контекста (когда он установлен через SetCurrentContext) и что для реального HttpContext обертка создается для каждого вызова Current?
Стивен Прайс
Да, это так. m_context имеет тип HttpContextBase и, возвращая HttpContextWrapper, возвращает HttpContextBase с текущим HttpContext
Энтони Шоу,
1
HttpContextManagerбыло бы лучше, чем имя, HttpContextSourceно я согласен HttpContextFactory, вводит в заблуждение.
профессор программирования
298

Вы можете «подделать это», создав новый 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();
Milox
источник
24
Мне нравится этот ответ лучше, чем принятый, потому что изменение производственного кода для поддержки ваших действий по тестированию является плохой практикой. Конечно, ваш производственный код должен абстрагировать сторонние пространства имен, подобные этому, но когда вы работаете с унаследованным кодом, у вас не всегда есть этот элемент управления или роскошь перефакторинга.
Шон Гловер
29
Вам не нужно использовать отражение для создания нового экземпляра HttpSessionState. Вы можете просто прикрепить ваш HttpSessionStateContainer к HttpContext, используя SessionStateUtility.AddHttpSessionStateToContext.
Брент М. Заклинание
MockHelper - это просто имя класса, в котором находится статический метод, вы можете использовать любое имя, какое пожелаете.
Милокс
Я пытался реализовать ваш ответ, но сессия все еще пуста. Не могли бы вы взглянуть на мой пост stackoverflow.com/questions/23586765/… . Спасибо
Джо
Server.MapPath()не будет работать, если вы используете это тоже.
Фу
45

Решение 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;
}
giammin
источник
Это позволяет вам подделать httpContext.Session, есть идеи, как сделать то же самое для httpContext.Application?
KyleMit
39

Я что-то об этом говорил некоторое время назад.

Модульное тестирование 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);
}
Ро Хит
источник
Работает так хорошо и просто ... Спасибо!
mggSoft
12

Если вы используете инфраструктуру MVC, это должно работать. Я использовал Милокса 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();
Nimblejoe
источник
3
Ссылка не работает, поэтому, возможно, разместите код здесь в следующий раз.
Rhyous
11

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

using (new FakeHttpContext())
{
   HttpContext.Current.Session["CustomerId"] = "customer1";       
}
VAD
источник
Прекрасно работает и очень прост в использовании
Beanwah
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();

https://stackoverflow.com/a/34022964/516748

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

new Mock<ISession>();
Строймеханизация
источник
7

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

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

так что вы можете использовать это:

HttpContextFactory.Current.Request.Headers.Add(key, value);
yzicus
источник
2

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

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

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

Исаак Альварадо
источник
1

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

Сначала я создал класс 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);
    }
}
Крис Хансон
источник
Мне очень нравится ваше решение. KISS => Сохраняйте это простым и глупым ;-)
CodeNotFound
1

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

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

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

_customObject.SessionProperty("CustomerId")

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

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

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

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

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

Синие облака
источник
0

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

Matt
источник
0

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

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;
  }
Ранджан Сингх
источник