2010-10-26 144 views
14

我想写一个Web服务的单元测试。我创建我的测试项目,引用我的Web项目(不是服务引用,程序集引用),然后编写一些代码来测试Web服务 - 它们工作正常。但是,有些服务通过使用HttpContext.Current.User.Identity.IsAuthenticated来确保用户已登录到Web应用程序。单元测试Web服务 - HttpContext

在测试的上下文中,没有像HttpContext这样的东西,所以测试总是失败。这些Web服务应该如何进行单元测试?

回答

25

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.WebSystem.Web.Abstractions的引用,其中HttpContextBase已定义。

+0

我也开始这样做(用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

+1

我没有使用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

2

如果使用的是嘲讽,你可以在另一个类包装这样的逻辑:

interface IAuthenticator 
{ 
    bool IsAuthenticated(); 
} 

并实现真正的一个:

​​

,但在测试中,创建一个模拟,并返回true或假:

Mock<IAuthenticator> mock = new Mock<IAuthenticator>(); 
mock.Expect(x => x.IsAuthenticated()).Returns(true); 
+0

要绝对正确我认为这应该是一个存根,而不是一个模拟。 – 2010-10-26 21:21:45

+0

你的意思是我们需要一个存根而不是模拟吗?我不同意,因为基于认证或不认证,服务会有不同的表现,因此我们需要期望能够返回。我不确定 - 也不那么热衷 - 存根和模拟的差异,但对我来说,这是一个模拟而不是存根。 – Aliostad 2010-10-26 21:25:31

+0

如果打算测试IAuthenticator接口是否正确调用,那么它应该是一个模拟。如果你想测试其他东西,它应该是一个存根。存根永远不会导致测试失败。他们只是为了让事情顺利进行。无论如何,我想这取决于你的模拟框架。在犀牛嘲笑模拟和存根之间有一个微妙的区别:http://stackoverflow.com/questions/463707/what-are-the-differences-between-mocks-and-stubs-on-rhino-mocks – 2010-10-26 23:09:48

1

你可能会考虑取决于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。

+0

你也可能考虑使用商用模拟工具,如Typemock Isolator或Telerik的JustMock,它可以减轻使用/管理Http *抽象的负担。 – 2010-10-26 21:44:10

0

我最后决定将财产上的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) 
0

基于上述解决方案,我实现了在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/