Moq: unit testing a method relying on HttpContext
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
.
How do I unit test a class that depends on HttpContext.Current and Sitecore.Context?
You would need to create a mock HTTPContext and inject it into the method for the test. (You probably need to mock quite a few other objects too, since the method has several dependencies.)
Then, after the method has run, assert that the the response in the context is as you want it.
See details here: Mock HttpContext.Current in Test Init Method
How to mock HttpContext.User
Make a mock http context
private class MockHttpContext : HttpContextBase {
private readonly IPrincipal user;
public MockHttpContext(IPrincipal principal) {
this.user = principal;
}
public override IPrincipal User {
get {
return user;
}
set {
base.User = value;
}
}
}
Arrange test accordingly.
[Test]
public void ViewDocuments_WhenCalled_ShouldReturnViewModel() {
// Arrange
var principal = new CustomPrincipal("2038786");
principal.UserId = "2038786";
principal.FirstName = "Test";
principal.LastName = "User";
principal.IsStoreUser = true;
var mockUoW = new Mock<IUnitOfWork>();
//...setup UoW dependency if needed
var controller = new DocumentsController(mockUoW.Object);
controller.ControllerContext = new ControllerContext {
Controller = controller,
HttpContext = new MockHttpContext(principal)
};
// Act
var result = controller.ViewDocuments();
//Assert
//...assertions
}
Don't mock system under test. Mock its dependencies.
How can I make HttpContext available to be used by my Unit Tests?
You shouldn't use HttpContext.Current
directly in your function as it is close to impossible to unit test, as you've already found out. I would suggest you using HttpContextBase instead, which is passed either in the constructor of your class or as an argument to the method you are testing. This will allow the consumers of this class to pass a real HttpContextWrapper and in your unit test you can mock the methods you need.
For example here's how you could call the method:
var wrapper = new HttpContextWrapper(HttpContext.Current);
Foo.UploadedFile(wrapper);
And in your unit test (using Rhino Mocks):
var contextMock = MockRepository.GenerateMock<HttpContextBase>();
// TODO: Define expectations on the mocked object
Foo.UploadedFile(contextMock);
Or, if you prefer, use Constructor Injection.
OpenRasta Mocking HttpContext in Unit Tests
You should not need to access HttpContext.Current
directly. You should be able to use one of the available dependencies which are easily mockable.
Setting HttpContext.Current.Session in a unit test
We had to mock HttpContext
by using a HttpContextManager
and calling the factory from within our application as well as the Unit Tests
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;
}
}
You would then replace any calls to HttpContext.Current
with HttpContextManager.Current
and have access to the same methods. Then when you're testing, you can also access the HttpContextManager
and mock your expectations
This is an example using 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;
}
and then to use it within your unit tests, I call this within my Test Init method
HttpContextManager.SetCurrentContext(GetMockedHttpContext());
you can then, in the above method add the expected results from Session that you're expecting to be available to your web service.
Related Topics
System.Badimageformatexception: Could Not Load File or Assembly
Sortedlist<>, Sorteddictionary<> and Dictionary<>
Datetime.Parse("2012-09-30T23:00:00.0000000Z") Always Converts to Datetimekind.Local
What Is Meant by "Managed" VS "Unmanaged" Resources in .Net
Transparent Background on Winforms
Escape Button to Close Windows Forms Form in C#
Step by Step Tutorial to Use Sap. Net Connector with VS 2008
Convert an Object to an Xml String
Connecting to SQL Server with Visual Studio Express Editions
Spinwait VS Sleep Waiting. Which One to Use
Multi Threading C# Application with SQL Server Database Calls
Using Webclient in C# How to Get the Url of a Site After Being Redirected
Lowering Priority of Task.Factory.Startnew Thread
Are Protected Members/Fields Really That Bad