Moq: unit testing a method relying on HttpContext

后端 未结 6 1019
情话喂你
情话喂你 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: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.

提交回复
热议问题