단위 테스트에서 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";

null이라는 예외 참조 HttpContext.Current.Session가 나타납니다.

단위 테스트 내에서 현재 세션을 초기화하는 방법이 있습니까?


이 방법 을 사용해 보셨습니까 ?
Raj Ranjhan

가능하면 HttpContextBase를 사용하십시오 .
jrummell

답변:


105

우리는 단위 테스트뿐만 아니라 응용 프로그램 내에서 HttpContexta를 사용 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.CurrentHttpContextManager.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());

그런 다음 위의 방법으로 웹 서비스에서 사용할 수있을 것으로 예상되는 Session의 예상 결과를 추가 할 수 있습니다.


1
그러나 이것은 SimpleWorkerRequest를 사용하지 않습니다
knocte

그는 자신의 SimpleWorkerRequest가 HttpContext의 값에 액세스 할 수 있도록 HttpContext를 모의하려고했습니다. 그는 자신의 서비스 내에서 HttpContextFactory를 사용합니다
Anthony Shaw

지원 필드 m_context가 모의 컨텍스트 (SetCurrentContext를 통해 설정된 경우)에 대해서만 반환되고 실제 HttpContext의 경우 Current에 대한 모든 호출에 대해 래퍼가 생성되는 것이 의도적 인 것입니까?
Stephen Price

네 그렇습니다. m_context 유형 HttpContextBase이며 반환은 HttpContextWrapper 현재 HttpContext를 함께 HttpContextBase을 반환
안토니 쇼

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(브렌트 M. 맞춤법의 의견에 따라) :

SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);

그런 다음 단위 테스트에서 다음과 같이 호출 할 수 있습니다.

HttpContext.Current = MockHelper.FakeHttpContext();

24
테스트 활동을 지원하기 위해 프로덕션 코드를 변경하는 것은 좋지 않기 때문에이 답변이 승인 된 것보다 낫습니다. 물론, 프로덕션 코드는 이와 같은 타사 네임 스페이스를 추상화해야하지만 레거시 코드로 작업 할 때 항상이 컨트롤이나 리팩토링을 할 수있는 것은 아닙니다.
Sean Glover

29
새로운 HttpSessionState 인스턴스를 생성하기 위해 리플렉션을 사용할 필요는 없습니다. SessionStateUtility.AddHttpSessionStateToContext를 사용하여 HttpSessionStateContainer를 HttpContext에 연결할 수 있습니다.
Brent M. Spell

MockHelper는 정적 메소드가있는 클래스의 이름 일뿐이므로 원하는 이름을 사용할 수 있습니다.
Milox

귀하의 답변을 구현하려고 시도했지만 세션이 여전히 null입니다. 내 Post stackoverflow.com/questions/23586765/…를 살펴보십시오 . 감사합니다
Joe

Server.MapPath()이 중 하나를 사용하면 작동하지 않습니다.
Yuck

45

Milox 솔루션 은 허용되는 IMHO보다 낫지 만 querystring으로 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

나는 얼마전에 이것에 대해 뭔가를 걱정한다.

MVC3 .NET에서 HttpContext.Current.Session의 단위 테스트

도움이 되길 바랍니다.

[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 프레임 워크를 사용하는 경우 이것이 작동합니다. Milox의 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();

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.

이것은 어떻게 이루어 집니까? 음 ... 의존성 주입!

따라서 테스트는 세션 (지하)을 설정 한 다음 세션에 대해 아무것도 모르는 것처럼 응용 프로그램 메서드를 호출 할 수 있습니다. 그런 다음 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수행 한 방법 의 마지막 줄에 사용자 자격 증명이 추가되고 인증 테스트에 사용할 수 있습니다.

또한 .MapPath()메소드 와 같이 HttpContext에 필요할 수있는 다른 부분이 있음을 알았습니다 . 여기설명 되어 있으며 NuGet을 통해 설치할 수 있는 FakeHttpContext가 있습니다 .



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;
  }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.