2010-10-26 19 views
14

Deseo escribir pruebas unitarias para un servicio web. Creé mi proyecto de prueba, hice referencia a mi proyecto web (no referencia de servicio, referencia de ensamblado), luego escribí código para probar los servicios web, funcionan bien. Sin embargo, hay algunos servicios que aseguran que el usuario inicie sesión en la aplicación web usando HttpContext.Current.User.Identity.IsAuthenticated.Servicios web de pruebas unitarias - HttpContext

En el contexto de las pruebas, no existe HttpContext, por lo que las pruebas siempre fallan. ¿Cómo deberían probarse estos tipos de servicios web en una unidad?

Respuesta

25

Here es una discusión relacionada.

Dejé de hacer referencia a HttpContext.Current directamente. y el uso de esta clase en su lugar:

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; 
    } 
} 

y utilizar HttpContextFactory.Current en lugar de HttpContext.Current en nuestro código.

Entonces se escribe esto en su ensayo:

 HttpContextFactory.SetCurrentContext(GetMockedHttpContext()); 

donde GetMockedHttpContext() es de here y se ve así:

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; 
    } 

Se utiliza un mocking framework llamada moq

En su proyecto de prueba debe agregar una referencia a System.Web y System.Web.Abstractions, donde HttpContextBase se define.

+0

También he empezado a hacer esto (reemplazando HttpContext con HttpContextFactory), y realmente me ayuda con Unit Testing (he envuelto estas capacidades en una API que se puede ver aquí http://o2platform.wordpress.com/2011/ 04/05/mocking-httpcontext-httprequest-and-httpresponse-for-unittests-using-moq /) –

+1

No estoy usando moq, así que esto no me llevó al 100%, pero fue útil. Miré los objetos falsos de Stephen Walther en busca de ayuda: http://stephenwalther.com/archive/2008/07/01/asp-net-mvc-tip-12-faking-the-controller-context.aspx –

2

Si está utilizando burla, se puede envolver esta lógica en otra clase:

interface IAuthenticator 
{ 
    bool IsAuthenticated(); 
} 

e implementar uno real:

class Authenticator : IAuthenticator 
{ 
    bool IsAuthenticated() 
    { 
     return HttpContext.Current.User.Identity.IsAuthenticated; 
    } 
} 

pero en la prueba, crear una maqueta y devolver verdadero o falso:

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

Para ser absolutamente correcto, creo que esto debería ser realmente un talón y no una burla. –

+0

¿Quiere decir que necesitamos un talón en lugar de simulacro? No estoy de acuerdo porque basado en autenticado o no, el servicio se comportará de manera diferente, por lo tanto, necesitamos expectativa para poder regresar. No estoy seguro, y no tan entusiasta, de las diferencias de un trozo y simulacro, pero para mí, eso es una burla y no trozo. – Aliostad

+0

Si la intención es probar que la interfaz IAuthenticator se llama correctamente, entonces debería ser una simulación. Si desea probar algo más, debe ser un talón. Los talones nunca harán que falle una prueba. Están allí para hacer que las cosas funcionen sin problemas. De todos modos, supongo que depende de tu marco de burla. En Rhino Mocks hay una sutil diferencia entre los simulacros y los talones: http://stackoverflow.com/questions/463707/what-are-the-differences-between-mocks-and-stubs-on-rhino-mocks –

1

Usted podría considerar la adopción de una dependencia en lugar de u System.Web.Abstractions.HttpContextBase cantar HttpContext.Current. El ensamblado System.Web.Abstractions tiene muchas clases comunes de ASP.NET Http * ya incluidas. Se usan en todo el código ASP.NET MVC. Si está utilizando un marco IoC/DI, es bastante fácil de usar. Por ejemplo, en Ninject:

Bind<HttpContextBase>.ToMethod(x => new HttpContextWrapper(HttpContext.Current)); 

y luego en su 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... 
    } 
} 

Esa es la manera simplista, pero espero que se entiende la idea ... Como se mencionó Aliostad, puede fácilmente simulacro HttpContextBase usando Rhino Mocks o Moq, etc. para probar SomeOperationNeedingAuthorization.

+0

También podría considere usar una herramienta de burla comercial, como Typemock Isolator o Telerik's JustMock, que evita el uso/administración de las abstracciones de Http *. –

0

me acabó poniendo una propiedad en el servicio 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 

Luego, en mi método de servicio Web:

<WebMethod()> _ 
Public Function GetProject(ByVal projectId As Int32) As String 

    If Me.Identity.IsAuthenticated Then 

    'code here 

    End If 

End Function 

Luego, en mi prueba (estoy usando 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

Sobre la base de la solución anterior, he implementado una clase contenedora en el O2 Platform que permite el uso fácil de estas clases de burla, por ejemplo, esta es la forma en que puedo escribir y leer desde el 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(); 

ver esta publicación en el blog para más detalles: http://o2platform.wordpress.com/2011/04/05/mocking-httpcontext-httprequest-and-httpresponse-for-unittests-using-moq/