Đặt HTTPContext.C hiện.Session trong một bài kiểm tra đơn vị


185

Tôi có một dịch vụ web tôi đang cố gắng để kiểm tra đơn vị. Trong dịch vụ, nó lấy một số giá trị HttpContextgiống như vậy:

 m_password = (string)HttpContext.Current.Session["CustomerId"];
 m_userID = (string)HttpContext.Current.Session["CustomerUrl"];

trong bài kiểm tra đơn vị tôi đang tạo bối cảnh bằng cách sử dụng một yêu cầu công nhân đơn giản, như vậy:

SimpleWorkerRequest request = new SimpleWorkerRequest("", "", "", null, new StringWriter());
HttpContext context = new HttpContext(request);
HttpContext.Current = context;

Tuy nhiên, bất cứ khi nào tôi cố gắng đặt các giá trị của HttpContext.Current.Session

HttpContext.Current.Session["CustomerId"] = "customer1";
HttpContext.Current.Session["CustomerUrl"] = "customer1Url";

Tôi nhận được ngoại lệ tham chiếu null mà nói HttpContext.Current.Sessionlà null.

Có cách nào để khởi tạo phiên hiện tại trong bài kiểm tra đơn vị không?


Bạn đã thử phương pháp này ?
Raj Ranjhan

Sử dụng HttpContextBase nếu bạn có thể.
jrummell

Câu trả lời:


105

Chúng tôi đã phải chế giễu HttpContextbằng cách sử dụng HttpContextManagervà gọi cho nhà máy từ trong ứng dụng của chúng tôi cũng như các Bài kiểm tra đơn vị

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

Sau đó, bạn sẽ thay thế bất kỳ cuộc gọi nào HttpContext.Currentbằng HttpContextManager.Currentvà có quyền truy cập vào cùng một phương thức. Sau đó, khi bạn đang thử nghiệm, bạn cũng có thể truy cập HttpContextManagervà chế giễu sự mong đợi của bạn

Đây là một ví dụ sử dụng 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;
}

và sau đó để sử dụng nó trong các bài kiểm tra đơn vị của bạn, tôi gọi nó trong phương thức Test init của tôi

HttpContextManager.SetCurrentContext(GetMockedHttpContext());

sau đó, bạn có thể, trong phương pháp trên, thêm các kết quả mong đợi từ Phiên mà bạn mong đợi sẽ có sẵn cho dịch vụ web của mình.


1
nhưng điều này không sử dụng SimpleWorkerRequest
knocte

anh ta đang cố gắng giả lập HTTPContext để SimpleWorkerRequest của anh ta có quyền truy cập vào các giá trị từ HttpContext, anh ta sẽ sử dụng HttpContextFactory trong dịch vụ của mình
Anthony Shaw

Có phải chủ ý là trường sao lưu m_context chỉ được trả về cho bối cảnh giả (khi được đặt qua SetCienContext) và đối với HTTPContext thực, trình bao bọc được tạo cho mỗi cuộc gọi đến Hiện tại?
Stephen Giá

Vâng, đúng vậy. m_context thuộc loại HttpContextBase và trả về HTTPContextWrapper trả về HTTPContextBase với httpContext hiện tại
Anthony Shaw

1
HttpContextManagersẽ là một cái tên tốt hơn HttpContextSourcenhưng tôi đồng ý HttpContextFactorylà sai lệch.
Giáo sư lập trình

298

Bạn có thể "giả mạo" bằng cách tạo một cái mới HttpContextnhư thế này:

http://www.necronet.org/archive/2010/07/28/unit-testing-code-that-uses-httpcontext-civerse-session.aspx

Tôi đã lấy mã đó và đưa nó vào một lớp trợ giúp tĩnh như vậy:

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

Hoặc thay vì sử dụng sự phản chiếu để xây dựng thể hiện mới HttpSessionState, bạn chỉ có thể đính kèm HttpSessionStateContainervào HttpContext(theo nhận xét của Brent M. Spell):

SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);

và sau đó bạn có thể gọi nó trong các bài kiểm tra đơn vị của bạn như:

HttpContext.Current = MockHelper.FakeHttpContext();

24
Tôi thích câu trả lời này tốt hơn câu trả lời được chấp nhận vì thay đổi mã sản xuất của bạn để hỗ trợ các hoạt động thử nghiệm của bạn là một thực tiễn tồi. Cấp, mã sản xuất của bạn nên trừu tượng hóa các không gian tên của bên thứ 3 như thế này, nhưng khi bạn làm việc với mã kế thừa, bạn không luôn có quyền kiểm soát này hoặc sự xa xỉ để tái xác định.
Sean Glover

29
Bạn không phải sử dụng sự phản chiếu để xây dựng thể hiện httpSessionState mới. Bạn chỉ có thể đính kèm httpSessionStateContainer của mình vào HTTPContext bằng cách sử dụng SessionStateUtility.AddHttpSessionStateToContext.
Brent M. Spell

MockHelper chỉ là tên của lớp có phương thức tĩnh, bạn có thể sử dụng bất kỳ tên nào bạn thích.
Milox

Tôi đã thử thực hiện câu trả lời của bạn nhưng Phiên vẫn không có giá trị. Bạn vui lòng xem qua bài viết stackoverflow.com/questions/23586765/ của tôi . Cảm ơn bạn
Joe

Server.MapPath()Sẽ không hiệu quả nếu bạn sử dụng cái này.
Yuck

45

Giải pháp Milox tốt hơn một IMHO được chấp nhận nhưng tôi gặp một số vấn đề với việc triển khai này khi xử lý các url với chuỗi truy vấn .

Tôi đã thực hiện một số thay đổi để làm cho nó hoạt động đúng với bất kỳ url nào và để tránh Reflection.

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

Điều này cho phép bạn giả mạo httpContext.Session, bất kỳ ý tưởng làm thế nào để làm tương tự cho httpContext.Application?
KyleMit

39

Tôi đã nói điều gì đó về điều này một thời gian trước đây.

Kiểm thử đơn vị httpContext.C hiện.Session trong MVC3 .NET

Hy vọng nó giúp.

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

Hoạt động rất tốt và đơn giản ... Cảm ơn!
mggSoft

12

Nếu bạn đang sử dụng khung MVC, nó sẽ hoạt động. Tôi đã sử dụng FakeHttpContext của Milox và thêm một vài dòng mã. Ý tưởng xuất phát từ bài đăng này:

http://codepaste.net/p269t8

Điều này dường như hoạt động trong MVC 5. Tôi chưa thử điều này trong các phiên bản trước của 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
Liên kết bị hỏng nên có thể đặt mã ở đây vào lần sau.
Rhyous

11

Bạn có thể thử FakeHttpContext :

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

Hoạt động tuyệt vời và rất đơn giản để sử dụng
Beanwah

7

Câu trả lời phù hợp với tôi là những gì @Anthony đã viết, nhưng bạn phải thêm một dòng khác

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

vì vậy bạn có thể sử dụng cái này:

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

7

Trong asp.net Core / MVC 6 rc2, bạn có thể đặt HttpContext

var SomeController controller = new SomeController();

controller.ControllerContext = new ControllerContext();
controller.ControllerContext.HttpContext = new DefaultHttpContext();
controller.HttpContext.Session = new DummySession();

RC 1 là

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

Cân nhắc sử dụng Moq

new Mock<ISession>();

2

Thử cái này:

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

Và thêm lớp:

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

Điều này sẽ cho phép bạn kiểm tra với cả phiên và bộ đệm.


1

Tôi đang tìm kiếm một cái gì đó ít xâm lấn hơn các tùy chọn được đề cập ở trên. Cuối cùng, tôi đã đưa ra một giải pháp tuyệt vời, nhưng nó có thể khiến một số người di chuyển nhanh hơn một chút.

Đầu tiên tôi tạo một lớp 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;
    }
}

Sau đó, tôi đã thêm một tham số tùy chọn vào hàm tạo của bộ điều khiển. Nếu tham số có mặt, sử dụng nó cho thao tác phiên. Nếu không, hãy sử dụng 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;
    }
}

Bây giờ tôi có thể đưa TestSession của mình vào bộ điều khiển:

class MyControllerTest
{

    private readonly MyController _controller;

    public MyControllerTest()
    {
        var testSession = new TestSession();
        var _controller = new MyController(testSession);
    }
}

Tôi thực sự thích giải pháp của bạn. KISS => Giữ cho nó đơn giản và ngu ngốc ;-)
CodeNotFound

1

Không bao giờ chế giễu .. không bao giờ! Giải pháp khá đơn giản. Tại sao giả như một sáng tạo đẹp như thếHttpContext ?

Đẩy phiên xuống! (Chỉ dòng này là đủ để hầu hết chúng ta hiểu nhưng được giải thích chi tiết bên dưới)

(string)HttpContext.Current.Session["CustomerId"];là cách chúng ta truy cập nó bây giờ. Thay đổi điều này thành

_customObject.SessionProperty("CustomerId")

Khi được gọi từ kiểm tra, _customObject sử dụng cửa hàng thay thế (giá trị khóa DB hoặc đám mây [ http://www.kvstore.io/] )

Nhưng khi được gọi từ ứng dụng thực, _customObjectsử dụngSession .

làm thế nào được thực hiện? cũng ... Tiêm phụ thuộc!

Vì vậy, kiểm tra có thể thiết lập phiên (ngầm) và sau đó gọi phương thức ứng dụng như thể nó không biết gì về phiên. Sau đó kiểm tra bí mật kiểm tra xem mã ứng dụng có cập nhật chính xác phiên không. Hoặc nếu ứng dụng hoạt động dựa trên giá trị phiên được đặt bởi thử nghiệm.

Thật ra, cuối cùng chúng tôi đã chế giễu mặc dù tôi nói: "không bao giờ chế giễu". Vì chúng tôi không thể giúp gì ngoài việc đi đến quy tắc tiếp theo, "chế giễu nơi nó làm tổn thương ít nhất!". Nhạo báng lớn HttpContexthoặc chế nhạo một phiên nhỏ, điều đó làm tổn thương ít nhất? đừng hỏi tôi những quy tắc này đến từ đâu Hãy để chúng tôi chỉ nói thông thường. Đây là một đọc thú vị về không chế nhạo vì bài kiểm tra đơn vị có thể giết chết chúng ta


0

Câu trả lời @Ro Hit đã giúp tôi rất nhiều, nhưng tôi đã thiếu thông tin đăng nhập của người dùng vì tôi phải giả mạo người dùng để kiểm tra đơn vị xác thực. Do đó, hãy để tôi mô tả cách tôi giải quyết nó.

Theo đó , nếu bạn thêm phương thức

    // using System.Security.Principal;
    GenericPrincipal FakeUser(string userName)
    {
        var fakeIdentity = new GenericIdentity(userName);
        var principal = new GenericPrincipal(fakeIdentity, null);
        return principal;
    }

và sau đó nối thêm

    HttpContext.Current.User = FakeUser("myDomain\\myUser");

đến dòng cuối cùng của TestSetupphương thức bạn đã thực hiện, thông tin đăng nhập của người dùng được thêm vào và sẵn sàng để được sử dụng để kiểm tra xác thực.

Tôi cũng nhận thấy rằng có những phần khác trong HttpContext mà bạn có thể yêu cầu, chẳng hạn như .MapPath()phương thức. Có một FakeHttpContext có sẵn, được mô tả ở đây và có thể được cài đặt qua NuGet.



0

Hãy thử cách này ..

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;
  }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.