我想写一个Web服务的单元测试。我创建我的测试项目,引用我的Web项目(不是服务引用,程序集引用),然后编写一些代码来测试Web服务 - 它们工作正常。但是,有些服务通过使用HttpContext.Current.User.Identity.IsAuthenticated
来确保用户已登录到Web应用程序。单元测试Web服务 - HttpContext
在测试的上下文中,没有像HttpContext这样的东西,所以测试总是失败。这些Web服务应该如何进行单元测试?
我想写一个Web服务的单元测试。我创建我的测试项目,引用我的Web项目(不是服务引用,程序集引用),然后编写一些代码来测试Web服务 - 它们工作正常。但是,有些服务通过使用HttpContext.Current.User.Identity.IsAuthenticated
来确保用户已登录到Web应用程序。单元测试Web服务 - HttpContext
在测试的上下文中,没有像HttpContext这样的东西,所以测试总是失败。这些Web服务应该如何进行单元测试?
Here是一个相关的讨论。
我停止直接引用HttpContext.Current
。并且使用这个类来代替:
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;
}
}
,并在我们的代码中使用HttpContextFactory.Current
代替HttpContext.Current
。
然后你在测试写:
HttpContextFactory.SetCurrentContext(GetMockedHttpContext());
其中GetMockedHttpContext()是here,看起来像这样:
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;
}
它使用了一个名为moq
在mocking framework您测试项目你必须添加对System.Web
和System.Web.Abstractions
的引用,其中HttpContextBase
已定义。
如果使用的是嘲讽,你可以在另一个类包装这样的逻辑:
interface IAuthenticator
{
bool IsAuthenticated();
}
并实现真正的一个:
,但在测试中,创建一个模拟,并返回true或假:
Mock<IAuthenticator> mock = new Mock<IAuthenticator>();
mock.Expect(x => x.IsAuthenticated()).Returns(true);
要绝对正确我认为这应该是一个存根,而不是一个模拟。 – 2010-10-26 21:21:45
你的意思是我们需要一个存根而不是模拟吗?我不同意,因为基于认证或不认证,服务会有不同的表现,因此我们需要期望能够返回。我不确定 - 也不那么热衷 - 存根和模拟的差异,但对我来说,这是一个模拟而不是存根。 – Aliostad 2010-10-26 21:25:31
如果打算测试IAuthenticator接口是否正确调用,那么它应该是一个模拟。如果你想测试其他东西,它应该是一个存根。存根永远不会导致测试失败。他们只是为了让事情顺利进行。无论如何,我想这取决于你的模拟框架。在犀牛嘲笑模拟和存根之间有一个微妙的区别:http://stackoverflow.com/questions/463707/what-are-the-differences-between-mocks-and-stubs-on-rhino-mocks – 2010-10-26 23:09:48
你可能会考虑取决于System.Web.Abstractions.HttpContextBase而不是你的依赖唱HttpContext.Current。 System.Web.Abstractions程序集有许多常用的ASP.NET Http *类已经包装给你。它们遍布ASP.NET MVC代码。如果您使用IoC/DI框架,使用起来非常简单。例如,在Ninject:
Bind<HttpContextBase>.ToMethod(x => new HttpContextWrapper(HttpContext.Current));
,然后在你的构造......
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...
}
}
这是过于简单的路,但我希望你的想法......至于Aliostad提到的,你可以很容易地模拟HttpContextBase使用Rhino Mocks或Moq等来测试SomeOperationNeedingAuthorization。
你也可能考虑使用商用模拟工具,如Typemock Isolator或Telerik的JustMock,它可以减轻使用/管理Http *抽象的负担。 – 2010-10-26 21:44:10
我最后决定将财产上的Web服务:
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
在我的web服务方法
然后:
<WebMethod()> _
Public Function GetProject(ByVal projectId As Int32) As String
If Me.Identity.IsAuthenticated Then
'code here
End If
End Function
然后在我的测试(我使用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)
基于上述解决方案,我实现了在O2 Platform允许轻松使用这些嘲讽类的包装类,例如这是我怎么能写,并从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();
看到这篇博客文章的更多细节:http://o2platform.wordpress.com/2011/04/05/mocking-httpcontext-httprequest-and-httpresponse-for-unittests-using-moq/
我也开始这样做(用HttpContextFactory替换HttpContext),它确实有助于单元测试(我将这些功能包装在一个API中,您可以在这里看到http://o2platform.wordpress.com/2011/ 04/05/mocking-httpcontext-httprequest-and-httpresponse-for-unittests-using-moq /) – 2011-04-05 11:11:50
我没有使用moq,所以这没有让我100%,但它是有帮助的。我看着Stephen Walther的假物件寻求帮助:http://stephenwalther.com/archive/2008/07/01/asp-net-mvc-tip-12-faking-the-controller-context.aspx – 2012-08-29 15:49:30