Встановлення 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.Sessionnull.

Чи є спосіб ініціалізувати поточний сеанс у рамках тестування одиниць?


Ви пробували цей метод ?
Радж Ранджан

Використовуйте 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(відповідно до коментаря Brent M. Spell):

SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);

а потім ви можете викликати це у своїх одиничних тестах:

HttpContext.Current = MockHelper.FakeHttpContext();

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

Це дозволяє підробляти 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 Milox і додав кілька додаткових рядків коду. Ідея виникла з цієї публікації:

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

3
Посилання розірвано, тому, можливо, поставте код тут наступного разу.
Rhyous

11

Ви можете спробувати FakeHttpContext :

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

Працює чудово і дуже просто у використанні
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);

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 використовує альтернативне зберігання (значення ключа DB або хмари [ http://www.kvstore.io/] )

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

як це робиться? ну ... Ін'єкційна залежність!

Тому тест може встановити сеанс (під землею), а потім викликати метод застосування так, ніби він нічого не знає про сеанс. Потім тест таємно перевіряє, чи правильно оновлений код програми. Або якщо програма веде себе на основі значення сеансу, встановленого тестом.

Насправді ми насправді насміхалися, хоча я сказав: "ніколи не знущайся". Тому що ми не могли не перейти до наступного правила: "глузуй там, де це найменше боляче!". Знущатися з величезного 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.



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;
  }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.