Mock HttpContext.Current in Test Init Method
Asked Answered
W

4

205

I'm trying to add unit testing to an ASP.NET MVC application I have built. In my unit tests I use the following code:

[TestMethod]
public void IndexAction_Should_Return_View() {
    var controller = new MembershipController();
    controller.SetFakeControllerContext("TestUser");

    ...
}

With the following helpers to mock the controller context:

public static class FakeControllerContext {
    public static HttpContextBase FakeHttpContext(string username) {
        var context = new Mock<HttpContextBase>();

        context.SetupGet(ctx => ctx.Request.IsAuthenticated).Returns(!string.IsNullOrEmpty(username));

        if (!string.IsNullOrEmpty(username))
            context.SetupGet(ctx => ctx.User.Identity).Returns(FakeIdentity.CreateIdentity(username));

        return context.Object;
    }

    public static void SetFakeControllerContext(this Controller controller, string username = null) {
        var httpContext = FakeHttpContext(username);
        var context = new ControllerContext(new RequestContext(httpContext, new RouteData()), controller);
        controller.ControllerContext = context;
    }
}

This test class inherits from a base class which has the following:

[TestInitialize]
public void Init() {
    ...
}

Inside this method it calls a library (which i have no control over) which tries to run the following code:

HttpContext.Current.User.Identity.IsAuthenticated

Now you can probably see the problem. I have set the fake HttpContext against the controller but not in this base Init method. Unit testing / mocking is very new to me so I want to make sure I get this right. What is the correct way for me to Mock out the HttpContext so that it is shared across my controller and any libraries which are called in my Init method.

Waite answered 7/12, 2010 at 17:8 Comment(0)
G
406

HttpContext.Current returns an instance of System.Web.HttpContext, which does not extend System.Web.HttpContextBase. 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.

The following code runs as expected, even in a console application:

HttpContext.Current = new HttpContext(
    new HttpRequest("", "http://tempuri.org", ""),
    new HttpResponse(new StringWriter())
    );

// User is logged in
HttpContext.Current.User = new GenericPrincipal(
    new GenericIdentity("username"),
    new string[0]
    );

// User is logged out
HttpContext.Current.User = new GenericPrincipal(
    new GenericIdentity(String.Empty),
    new string[0]
    );
Gore answered 7/12, 2010 at 17:18 Comment(8)
Cheers but how could i set this for a logged out user?Waite
@Waite - If you pass an empty string into the GenericIdentity constructor, IsAuthenticated will return falseGore
Could this be used to mock Cache in the HttpContext?Leopard
Hi @RichardSzalay, I added this snippet of code to my test then invoked my controller method but the test fails because 'User.Identity.Name' is null. I assumed this code snippet would cause this value to be populated, am I wrong or missing something?Sindee
@CiaranG - MVC uses HttpContextBase, which can be mocked. There's no need to use the workaround I posted if you are using MVC. If you go ahead with it, you probably need to run the code I posted before you even create the controller.Gore
How to set IP / Browser on HttpRequest?Crossroads
That creates an HttpApplicationState, but I can't add anything to the indexer (i.e. Application["name"] can't be set).Locution
How do we stub the Session property under this approach?Mortise
P
43

Below Test Init will also do the job.

[TestInitialize]
public void TestInit()
{
  HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
  YourControllerToBeTestedController = GetYourToBeTestedController();
}
Player answered 9/5, 2014 at 16:32 Comment(5)
I cannot get addressibility to HTTPContext in a separate Test project in my solution. Are you able to get it via inheriting a controller?Encomiast
Do you have reference to System.Web in your test project?Player
Yes but my project is a MVC project, is it possible that the MVC version of System.Web only contains a subset of that namespace?Encomiast
@user1522548 (you should create an account) Assembly System.Web.dll, v4.0.0.0 definitely has HTTPContext i just checked my source code.Player
My mistake, I has a reference in the file to System.Web.MVC and NOT System.Web. Thanks for your help.Encomiast
M
11

I know this is an older subject, however Mocking a MVC application for unit tests is something we do on very regular basis.

I just wanted to add my experiences Mocking a MVC 3 application using Moq 4 after upgrading to Visual Studio 2013. None of the unit tests were working in debug mode and the HttpContext was showing "could not evaluate expression" when trying to peek at the variables.

Turns out visual studio 2013 has issues evaluating some objects. To get debugging mocked web applications working again, I had to check the "Use Managed Compatibility Mode" in Tools=>Options=>Debugging=>General settings.

I generally do something like this:

public static class FakeHttpContext
{
    public static void SetFakeContext(this Controller controller)
    {

        var httpContext = MakeFakeContext();
        ControllerContext context =
        new ControllerContext(
        new RequestContext(httpContext,
        new RouteData()), controller);
        controller.ControllerContext = context;
    }


    private static HttpContextBase MakeFakeContext()
    {
        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>();

        context.Setup(c=> c.Request).Returns(request.Object);
        context.Setup(c=> c.Response).Returns(response.Object);
        context.Setup(c=> c.Session).Returns(session.Object);
        context.Setup(c=> c.Server).Returns(server.Object);
        context.Setup(c=> c.User).Returns(user.Object);
        user.Setup(c=> c.Identity).Returns(identity.Object);
        identity.Setup(i => i.IsAuthenticated).Returns(true);
        identity.Setup(i => i.Name).Returns("admin");

        return context.Object;
    }


}

And initiating the context like this

FakeHttpContext.SetFakeContext(moController);

And calling the Method in the controller straight forward

long lReportStatusID = -1;
var result = moController.CancelReport(lReportStatusID);
Millur answered 5/5, 2016 at 20:13 Comment(3)
Is there a good reason to set it this way versus the accepted answer? Off the bat this seems more complicated and doesn't seem to offer any extra benefit.Kamikamikaze
It offers a more detailed/modular mocking methodFallible
What do I do if my code under test references HttpContext.Current directly, and doesn't inject the base dependency?Mortise
M
6

If your application third party redirect internally, so it is better to mock HttpContext in below way :

HttpWorkerRequest initWorkerRequest = new SimpleWorkerRequest("","","","",new StringWriter(CultureInfo.InvariantCulture));
System.Web.HttpContext.Current = new HttpContext(initWorkerRequest);
System.Web.HttpContext.Current.Request.Browser = new HttpBrowserCapabilities();
System.Web.HttpContext.Current.Request.Browser.Capabilities = new Dictionary<string, string> { { "requiresPostRedirectionHandling", "false" } };
Maize answered 11/3, 2016 at 11:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.