it-swarm.com.ru

Установка 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 является нулем. 

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

164
DaveB

Мы должны были смоделировать 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());

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

95
Anthony Shaw

Вы можете «подделать это», создав новую 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();
268
Milox

решение 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;
}
39
giammin

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

Модульное тестирование 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);
}
35
Ro Hit

Если вы используете инфраструктуру 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();
12
Nimblejoe

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

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

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

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

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

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

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

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

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

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

_customObject.SessionProperty("CustomerId")

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

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

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

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

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

1
Blue Clouds

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

        // 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
Isaac Alvarado

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

Сначала я создал TestSession class:

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);
    }
}
1
Chris Hanson

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

0
Matt

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

0
Lars Ladegaard