15

I want to write unit tests for a web service. I create my test project, reference my web project (not service reference, assembly reference), then write some code to test the web services - they work fine. However, there are some services which make sure the user is logged in to the web application by using HttpContext.Current.User.Identity.IsAuthenticated.

In the context of the tests, there is no such thing as HttpContext, so the tests always fail. How should these kinds of web services be unit tested?

Brandon Montgomery
  • 6,924
  • 3
  • 48
  • 71

5 Answers5

26

Here is a related discussion.

I stopped referencing HttpContext.Current directly. and use this class instead:

public class HttpContextFactory
{
    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;
    }
}

and use HttpContextFactory.Current instead of HttpContext.Current in our code.

Then you write this in your test:

        HttpContextFactory.SetCurrentContext(GetMockedHttpContext());

where GetMockedHttpContext() is from here and looks like this:

    private System.Web.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>();

        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(x => x.Identity).Returns(identity.Object);
        identity.Setup(id => id.IsAuthenticated).Returns(true);
        identity.Setup(id => id.Name).Returns("test");

        return context.Object;
    }

It uses a mocking framework called moq

In your test project you have to add a reference to System.Web and System.Web.Abstractions, where HttpContextBase is defined.

Community
  • 1
  • 1
Skuli
  • 1,997
  • 2
  • 20
  • 28
  • I've also started doing this (replacing HttpContext with HttpContextFactory), and it really helps with the Unit Testing (I've wrapped this capabilities in an API which you can see here http://o2platform.wordpress.com/2011/04/05/mocking-httpcontext-httprequest-and-httpresponse-for-unittests-using-moq/) – Dinis Cruz Apr 05 '11 at 11:11
  • 1
    I'm not using moq, so this didn't get me to 100%, but it was helpful. I looked at Stephen Walther's fake objects for help: http://stephenwalther.com/archive/2008/07/01/asp-net-mvc-tip-12-faking-the-controller-context.aspx – Don Rolling Aug 29 '12 at 15:49
3

If you are using mocking, you can wrap this logic in another class:

interface IAuthenticator
{
   bool IsAuthenticated();
}

and implement the real one:

class Authenticator : IAuthenticator
{
   bool IsAuthenticated()
   {
      return HttpContext.Current.User.Identity.IsAuthenticated;
   }
}

but in the test, create a mock and return true or false:

Mock<IAuthenticator> mock = new Mock<IAuthenticator>();
mock.Expect(x => x.IsAuthenticated()).Returns(true);
Aliostad
  • 80,612
  • 21
  • 160
  • 208
  • To be absolutely correct I think this should actually be a stub and not a mock. – Jakob Christensen Oct 26 '10 at 21:21
  • Do you mean we need a stub instead of mock? I would disagree because based on authenticated or not, service will behave differently hence we need expectation to be able to return. I am not sure - and not that keen - on the differences of a stub and mock but for me, that is a mock and not stub. – Aliostad Oct 26 '10 at 21:25
  • If the intention is to test that the IAuthenticator interface is called correctly then it should be a mock. If you wish to test something else it should be a stub. Stubs will never cause a test to fail. They are just there to make things run smoothly. Anyway I guess it depends on your mocking framework. In Rhino Mocks there is a subtle difference between mocks and stubs: http://stackoverflow.com/questions/463707/what-are-the-differences-between-mocks-and-stubs-on-rhino-mocks – Jakob Christensen Oct 26 '10 at 23:09
2

You might consider taking a dependency on System.Web.Abstractions.HttpContextBase instead of using HttpContext.Current. The System.Web.Abstractions assembly has a lot of common ASP.NET Http* classes already wrapped for you. They're used all over the ASP.NET MVC code. If you're using an IoC/DI framework, it's pretty easy to use. For example, in Ninject:

Bind<HttpContextBase>.ToMethod(x => new HttpContextWrapper(HttpContext.Current));

and then in your constructor...

public class SomeWebService
{
    private HttpContextBase _httpContext;

    public SomeWebService(HttpContextBase httpContext)
    {
        _httpContext = httpContext;
    }

    public void SomeOperationNeedingAuthorization()
    {
        IIdentity userIdentity = _httpContext.User.Identity;

        if (!userIdentity.IsAuthenticated)
            return;

        // Do something here...
    }
}

That's WAY oversimplified, but I hope you get the idea... As Aliostad mentioned, you can easily mock HttpContextBase using Rhino Mocks or Moq, etc. to test SomeOperationNeedingAuthorization.

Andy S
  • 8,641
  • 6
  • 36
  • 40
  • You might also consider using a commercial mocking tool, such as Typemock Isolator or Telerik's JustMock, which alleviates from using/managing Http* abstractions. – Andy S Oct 26 '10 at 21:44
1

I ended up putting a property on the web service:

Private mIdentity As System.Security.Principal.IIdentity
Public Property Identity() As System.Security.Principal.IIdentity
  Get
    If mIdentity Is Nothing Then mIdentity = HttpContext.Current.User.Identity
    Return mIdentity
  End Get
  Set(ByVal value As System.Security.Principal.IIdentity)
    mIdentity = value
  End Set
End Property

Then in my web service method:

<WebMethod()> _
Public Function GetProject(ByVal projectId As Int32) As String

  If Me.Identity.IsAuthenticated Then

    'code here

  End If

End Function

Then in my test (I'm using RhinoMocks):

Dim mockery As New MockRepository()
Dim mockIdentity As System.Security.Principal.IIdentity = mockery.DynamicMock(Of System.Security.Principal.IIdentity)()

Dim projectService As New TeamDynamix.Enterprise.Web.Next.ProjectService()
projectService.Identity = mockIdentity
mockIdentity.Stub(Function(i As System.Security.Principal.IIdentity) i.IsAuthenticated).Return(True)
Brandon Montgomery
  • 6,924
  • 3
  • 48
  • 71
0

Based on the solution above, I've implemented a wrapper class in the O2 Platform that allows the easy use of these mocking classes, for example this is how I can write and read from the HttpRequest.InputStream

var mockHttpContext = new API_Moq_HttpContext();
var httpContext = mockHttpContext.httpContext();
httpContext.request_Write("<html><body>".line());
httpContext.request_Write("   this is a web page".line());  
httpContext.request_Write("</body></html>"); 
return httpContext.request_Read();

see this blog post for more details: http://o2platform.wordpress.com/2011/04/05/mocking-httpcontext-httprequest-and-httpresponse-for-unittests-using-moq/

Dinis Cruz
  • 4,161
  • 2
  • 31
  • 49