Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Moq: unit testing a method relying on HttpContext

Consider a method in a .NET assembly:

public static string GetSecurityContextUserName() {               //extract the username from request                string sUser = HttpContext.Current.User.Identity.Name;  //everything after the domain       sUser = sUser.Substring(sUser.IndexOf("\\") + 1).ToLower();   return sUser;       } 

I'd like to call this method from a unit test using the Moq framework. This assembly is part of a webforms solution. The unit test looks like this, but I am missing the Moq code.

//arrange   string ADAccount = "BUGSBUNNY";  string fullADName = "LOONEYTUNES\BUGSBUNNY";    //act      //need to mock up the HttpContext here somehow -- using Moq.  string foundUserName = MyIdentityBL.GetSecurityContextUserName();   //assert  Assert.AreEqual(foundUserName, ADAccount, true, "Should have been the same User Identity."); 

Question:

  • How can I use Moq to arrange a fake HttpContext object with some value like 'MyDomain\MyUser'?
  • How do I associate that fake with my call into my static method at MyIdentityBL.GetSecurityContextUserName()?
  • Do you have any suggestions on how to improve this code/architecture?
like image 501
p.campbell Avatar asked Jul 31 '09 18:07

p.campbell


People also ask

Is Moq a unit test?

The Moq framework is an open source unit testing framework that works very well with . NET code and Phil shows us how to use it.

Can we mock HttpContext?

HttpContextBase was added later to address HttpContext being difficult to mock. The two classes are basically unrelated ( HttpContextWrapper is used as an adapter between them). Fortunately, HttpContext itself is fakeable just enough for you do replace the IPrincipal (User) and IIdentity .

What is Moq testing framework?

Moq is a mocking framework built to facilitate the testing of components with dependencies. As shown earlier, dealing with dependencies could be cumbersome because it requires the creation of test doubles like fakes. Moq makes the creation of fakes redundant by using dynamically generated types.

Can you mock a private method Moq?

Moq supports mocking protected methods. Changing the methods to protected , instead of private , would allow you to mock their implementation.


2 Answers

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.

like image 134
womp Avatar answered Sep 28 '22 16:09

womp


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.

like image 33
Sly Gryphon Avatar answered Sep 28 '22 17:09

Sly Gryphon