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 te开发者_如何学运维st 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?
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.
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);
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.
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)
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/
精彩评论