การตั้งค่า 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

เราต้องล้อเลียน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 ภายในบริการของเขา
Anthony Shaw

มันเป็นความตั้งใจหรือไม่ที่เขตข้อมูลสำรอง m_context จะถูกส่งกลับสำหรับบริบทจำลองเท่านั้น (เมื่อตั้งค่าผ่าน SetCurrentContext) และสำหรับ HttpContext จริงนั้น wrapper จะถูกสร้างขึ้นสำหรับการเรียกไปยังปัจจุบันทุกครั้งหรือไม่
สตีเฟ่นราคา

ใช่แล้ว. m_context เป็นประเภท HttpContextBase และส่งคืน HttpContextWrapper ส่งคืน HttpContextBase ด้วย HttpContext ปัจจุบัน
Anthony Shaw

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
ฉันชอบคำตอบนี้ดีกว่าคำตอบที่ยอมรับได้เนื่องจากการเปลี่ยนรหัสการผลิตของคุณเพื่อสนับสนุนกิจกรรมการทดสอบของคุณเป็นแนวปฏิบัติที่ไม่ดี ได้รับรหัสการผลิตของคุณควรสรุปออกเป็นเนมสเปซของบุคคลที่สามเช่นนี้ แต่เมื่อคุณทำงานกับรหัสดั้งเดิมคุณจะไม่มีตัวควบคุมนี้หรือความหรูหราในการพิจารณาปัจจัย
Sean Glover

29
คุณไม่จำเป็นต้องใช้การสะท้อนเพื่อสร้างอินสแตนซ์ HttpSessionState ใหม่ คุณสามารถแนบ HttpSessionStateContainer ของคุณกับ HttpContext โดยใช้ SessionStateUtility.AddHttpSessionStateToContext
Brent M. Spell

MockHelper เป็นเพียงชื่อของคลาสที่เป็นวิธีการแบบคงที่คุณสามารถใช้ชื่อใดก็ได้ที่คุณต้องการ
Milox

ฉันได้ลองใช้คำตอบของคุณแล้ว แต่เซสชันก็ยังว่างอยู่ คุณช่วยกรุณาดูที่โพสต์stackoverflow.com/questions/23586765/ของฉันได้ไหม ขอบคุณ
Joe

Server.MapPath()จะไม่ทำงานถ้าคุณใช้สิ่งนี้
Yuck

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

ฉัน worte บางสิ่งบางอย่างเกี่ยวกับเรื่องนี้ในขณะที่ผ่านมา

หน่วยทดสอบ 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


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

คำตอบที่ทำงานร่วมกับฉันคือสิ่งที่ @Anony เขียนไว้ แต่คุณต้องเพิ่มอีกบรรทัดซึ่งก็คือ

    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;
  }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.