Moq: unit testing a method relying on HttpContext

后端 未结 6 991
情话喂你
情话喂你 2020-11-29 03:53

Consider a method in a .NET assembly:

public static string GetSecurityContextUserName()
{             
 //extract the username from request              
 st         


        
相关标签:
6条回答
  • 2020-11-29 04:20

    This is not really related in using Moq for unit testing of what you need.

    Generally we at work have a layered architecture, where the code on the presentation layer is really just for arranging things for being displayed on the UI. This kind of code is not covered with unit tests. All the rest of the logic resides on the business layer, which doesn't have to have any dependency on the presentation layer (i.e. UI specific references such as the HttpContext) since the UI may also be a WinForms application and not necessarily a web application.

    In this way you can avoid to mess around with Mock frameworks, trying to simulate HttpRequests etc...although often it may still be necessary.

    0 讨论(0)
  • 2020-11-29 04:26

    In general for ASP.NET unit testing, rather than accessing HttpContext.Current you should have a property of type HttpContextBase whose value is set by dependency injection (such as in the answer provided by Womp).

    However, for testing security related functions I would recommend using Thread.CurrentThread.Principal (instead of HttpContext.Current.User). Using Thread.CurrentThread has the advantage of also being reusable outside a web context (and works the same in a web context because the ASP.NET framework always sets both values the same).

    To then test Thread.CurrentThread.Principal I usually use a scope class that sets the Thread.CurrentThread to a test value and then resets on dispose:

    using (new UserResetScope("LOONEYTUNES\BUGSBUNNY")) {
        // Put test here -- CurrentThread.Principal is reset when PrincipalScope is disposed
    }
    

    This fits well with the standard .NET security component -- where a component has a known interface (IPrincipal) and location (Thread.CurrentThread.Principal) -- and will work with any code that correctly uses/checks against Thread.CurrentThread.Principal.

    A base scope class would be something like the following (adjust as necessary for things like adding roles):

    class UserResetScope : IDisposable {
        private IPrincipal originalUser;
        public UserResetScope(string newUserName) {
            originalUser = Thread.CurrentPrincipal;
            var newUser = new GenericPrincipal(new GenericIdentity(newUserName), new string[0]);
            Thread.CurrentPrincipal = newUser;
        }
        public IPrincipal OriginalUser { get { return this.originalUser; } }
        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing) {
            if (disposing) {
                Thread.CurrentPrincipal = originalUser;
            }
        }
    }
    

    Another alternative is, instead of using the standard security component location, write your app to use injected security details, e.g. add an ISecurityContext property with a GetCurrentUser() method or similar, and then use that consistently throughout your application -- but if you are going to do this in the context of a web application then you might as well use the pre-built injected context, HttpContextBase.

    0 讨论(0)
  • 2020-11-29 04:27

    Have a look at this http://haacked.com/archive/2007/06/19/unit-tests-web-code-without-a-web-server-using-httpsimulator.aspx

    Using httpSimulator class,You will be able to do pass a HttpContext to handler

    HttpSimulator sim = new HttpSimulator("/", @"C:\intepub\?")
    .SimulateRequest(new Uri("http://localhost:54331/FileHandler.ashx?
    ticket=" + myticket + "&fileName=" + path));
    
    FileHandler fh = new FileHandler();
    fh.ProcessRequest(HttpContext.Current);
    

    HttpSimulator implement what we need to get a HttpContext instance. So you don't need to use Moq here.

    0 讨论(0)
  • 2020-11-29 04:29

    Webforms is notoriously untestable for this exact reason - a lot of code can rely on static classes in the asp.net pipeline.

    In order to test this with Moq, you need to refactor your GetSecurityContextUserName() method to use dependency injection with an HttpContextBase object.

    HttpContextWrapper resides in System.Web.Abstractions, which ships with .Net 3.5. It is a wrapper for the HttpContext class, and extends HttpContextBase, and you can construct an HttpContextWrapper just like this:

    var wrapper = new HttpContextWrapper(HttpContext.Current);
    

    Even better, you can mock an HttpContextBase and set up your expectations on it using Moq. Including the logged in user, etc.

    var mockContext = new Mock<HttpContextBase>();
    

    With this in place, you can call GetSecurityContextUserName(mockContext.Object), and your application is much less coupled to the static WebForms HttpContext. If you're going to be doing a lot of tests that rely on a mocked context, I highly suggest taking a look at Scott Hanselman's MvcMockHelpers class, which has a version for use with Moq. It conveniently handles a lot of the setup necessary. And despite the name, you don't need to be doing it with MVC - I use it successfully with webforms apps when I can refactor them to use HttpContextBase.

    0 讨论(0)
  • 2020-11-29 04:34
    [TestInitialize]
    public void TestInit()
    {
      HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
    }
    

    Also you can moq like below

    var controllerContext = new Mock<ControllerContext>();
          controllerContext.SetupGet(p => p.HttpContext.Session["User"]).Returns(TestGetUser);
          controllerContext.SetupGet(p => p.HttpContext.Request.Url).Returns(new Uri("http://web1.ml.loc"));
    
    0 讨论(0)
  • 2020-11-29 04:40

    If you're using the CLR security model (as we do) then you'll need to use some abstracted functions to get and set the current principal if you want to allow testing, and use these whenever getting or setting the principal. Doing this allows you to get/set the principal wherever is relevant (typically on HttpContext on the web, and on the current thread elsewhere like unit tests). This would look something like:

    public static IPrincipal GetCurrentPrincipal()
    {
        return HttpContext.Current != null ?
            HttpContext.Current.User :
            Thread.CurrentThread.Principal;
    }
    
    public static void SetCurrentPrincipal(IPrincipal principal)
    {
         if (HttpContext.Current != null) HttpContext.Current.User = principal'
         Thread.CurrentThread.Principal = principal;
    }
    

    If you use a custom principal then these can be fairly nicely integrated into its interface, for example below Current would call GetCurrentPrincipal and SetAsCurrent would call SetCurrentPrincipal.

    public class MyCustomPrincipal : IPrincipal
    {
        public MyCustomPrincipal Current { get; }
        public bool HasCurrent { get; }
        public void SetAsCurrent();
    }
    
    0 讨论(0)
提交回复
热议问题