2010-10-26 87 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/