Установка HttpContext.Current.Session в модульном тесте - PullRequest
172 голосов
/ 09 марта 2012

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

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

Ответы [ 14 ]

280 голосов
/ 12 апреля 2012

Вы можете «подделать это», создав новый 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 (согласно комментарию Брента М. Заклинания):

SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);

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

HttpContext.Current = MockHelper.FakeHttpContext();
99 голосов
/ 09 марта 2012

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

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

42 голосов
/ 31 октября 2013

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;
}
36 голосов
/ 31 октября 2013

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

Юнит-тестирование 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);
}
12 голосов
/ 01 ноября 2014

Если вы используете инфраструктуру 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();
11 голосов
/ 27 июля 2015

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

using (new FakeHttpContext())
{
   HttpContext.Current.Session["CustomerId"] = "customer1";       
}
7 голосов
/ 18 мая 2016

В 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 голосов
/ 24 сентября 2013

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

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

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

HttpContextFactory.Current.Request.Headers.Add(key, value);
1 голос
/ 11 апреля 2018

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

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

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

_customObject.SessionProperty("CustomerId")

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

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

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

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

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

1 голос
/ 24 января 2017

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

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