Установив Свойство HttpContext.Текущий.Сеанс в модульном тесте


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

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

13 160

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 и высмеивать ваши ожидания

это пример использования упаковка:

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

и затем использовать его в ваших модульных тестов, я называю это в моем Метод испытания инит

HttpContextManager.SetCurrentContext(GetMockedHttpContext());

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

вы можете "подделать его", создав новый 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's комментарий):

SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);

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

HttpContext.Current = MockHelper.FakeHttpContext();

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

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

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

Модульное Тестирование HttpContext.Текущий.Сеанс в 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);
}

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

http://codepaste.net/p269t8

Это, кажется, работает в MVC 5. Я не пробовал это в более ранних версиях аварии.

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

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

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

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

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

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

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

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

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

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

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

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

сначала я создал 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.Сессия:

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

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

нажмите сеанс вниз! (Только эта строка достаточно для большинства из нас, чтобы понять, но подробно объяснил ниже)

(string)HttpContext.Current.Session["CustomerId"]; Как мы его сейчас. Измените это на

_customObject.SessionProperty("CustomerId")

при вызове из теста _customObject использует альтернативное хранилище (DB или cloud key value[ http://www.kvstore.io/] )

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

как это сделать? что ж... Инъекция Зависимости!

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

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

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

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