views:

44

answers:

4

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?

+1  A: 

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
To be absolutely correct I think this should actually be a stub and not a mock.
Jakob Christensen
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
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
A: 

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
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
A: 

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
+1  A: 

Here is a related discussion.

I stopped referencing HttpContext.Current directly. and use this class instead ( See this blog ):

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.Expect(ctx => ctx.Request).Returns(request.Object);
        context.Expect(ctx => ctx.Response).Returns(response.Object);
        context.Expect(ctx => ctx.Session).Returns(session.Object);
        context.Expect(ctx => ctx.Server).Returns(server.Object);
        context.Expect(ctx => ctx.User).Returns(user.Object);
        user.Expect(ctx => ctx.Identity).Returns(identity.Object);
        identity.Expect(id => id.IsAuthenticated).Returns(true);
        identity.Expect(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.

Skúli