2009-07-31 11 views
35

Considere un método en un ensamblado de .NET:Moq: unidad de evaluación de un método depender de HttpContext

public static string GetSecurityContextUserName() 
{    
//extract the username from request    
string sUser = HttpContext.Current.User.Identity.Name; 
//everything after the domain  
sUser = sUser.Substring(sUser.IndexOf("\\") + 1).ToLower(); 

return sUser;  
} 

me gustaría llamar a este método desde una unidad de prueba utilizando el marco Moq. Este ensamblaje es parte de una solución de formularios web. La prueba unitaria se ve así, pero me falta el código Moq.

//arrange 
string ADAccount = "BUGSBUNNY"; 
string fullADName = "LOONEYTUNES\BUGSBUNNY"; 

//act  
//need to mock up the HttpContext here somehow -- using Moq. 
string foundUserName = MyIdentityBL.GetSecurityContextUserName(); 

//assert 
Assert.AreEqual(foundUserName, ADAccount, true, "Should have been the same User Identity."); 

Pregunta:

  • ¿Cómo puedo usar Moq para organizar un objeto HttpContext falsa con algún valor como 'MiDominio \ MyUser'?
  • ¿Cómo asocio esa falsificación con mi llamada a mi método estático al MyIdentityBL.GetSecurityContextUserName()?
  • ¿Tiene alguna sugerencia sobre cómo mejorar este código/arquitectura?

Respuesta

39

Formas web es notoriamente imposible de verificar por esta razón exacta: una gran cantidad de código puede depender de clases estáticas en la tubería asp.net.

Para probar esto con Moq, debe refactorizar su método GetSecurityContextUserName() para usar la inyección de dependencia con un objeto HttpContextBase.

HttpContextWrapper reside en System.Web.Abstractions, que se entrega con .Net 3.5. Es un contenedor para la clase HttpContext, y se extiende HttpContextBase, y se puede construir un HttpContextWrapper como este:

var wrapper = new HttpContextWrapper(HttpContext.Current); 

Aún mejor, puede burlarse de un HttpContextBase y establecer sus expectativas en él usando Moq. Incluyendo el usuario conectado, etc.

var mockContext = new Mock<HttpContextBase>(); 

Con esto en su lugar, puede llamar GetSecurityContextUserName(mockContext.Object), y su aplicación es mucho menos junto a los formularios Web estáticas HttpContext. Si va a hacer muchas pruebas que se basan en un contexto burlado, le sugiero encarecidamente taking a look at Scott Hanselman's MvcMockHelpers class, que tiene una versión para usar con Moq. Maneja convenientemente una gran cantidad de la configuración necesaria. Y a pesar del nombre, no es necesario que lo hagas con MVC: lo uso con éxito con aplicaciones webforms cuando puedo refactorizarlas para usar HttpContextBase.

+0

No se puede utilizar la nueva HttpContextBase(), ya que es una clase abstracta (como su nombre indica). En su lugar, debe usar HttpContextWrapper() nuevo. –

+0

Gracias ... mi cerebro estaba por encima de mis manos cuando contesté esto, supongo;) – womp

0

Si está utilizando el modelo de seguridad CLR (como lo hacemos nosotros), entonces necesitará usar algunas funciones abstractas para obtener y configurar el principal actual si desea permitir las pruebas, y usarlas siempre que obtenga o configure el director de escuela. Hacer esto le permite obtener/configurar el principal donde sea relevante (generalmente en HttpContext en la web, y en el hilo actual en otras partes, como pruebas unitarias). Esto sería algo como:

public static IPrincipal GetCurrentPrincipal() 
{ 
    return HttpContext.Current != null ? 
     HttpContext.Current.User : 
     Thread.CurrentThread.Principal; 
} 

public static void SetCurrentPrincipal(IPrincipal principal) 
{ 
    if (HttpContext.Current != null) HttpContext.Current.User = principal' 
    Thread.CurrentThread.Principal = principal; 
} 

Si utiliza un principal personalizado, entonces estos pueden ser bastante bien integrado en su interfaz, por ejemplo por debajo Current llamaría GetCurrentPrincipal y SetAsCurrent llamaría SetCurrentPrincipal.

public class MyCustomPrincipal : IPrincipal 
{ 
    public MyCustomPrincipal Current { get; } 
    public bool HasCurrent { get; } 
    public void SetAsCurrent(); 
} 
+0

Este código es innecesario porque si reflexionas sobre el código ASP.NET verás que HttpContext.Current.User y Thread.CurrentThread.Principal siempre se configuran con el mismo valor, por lo que solo es necesario comprobar Thread.CurrentThread. Esto incluso se ve en el código anterior, donde SetCurrentPrincipal siempre establece Thread.CurrentPrincipal, además de las actualizaciones HttpContext (si existe) para ser el mismo; en ningún momento tienen valores diferentes. (En otras palabras, siempre use Thread.CurrentThread). –

+2

@Sly - Son lo mismo, excepto cuando no lo son. http://www.hanselman.com/blog/SystemThreadingThreadCurrentPrincipalVsSystemWebHttpContextCurrentUserOrWhyFormsAuthenticationCanBeSubtle.aspx –

0

Esto no está realmente relacionado con el uso de Moq para probar las unidades de lo que necesita.

Generalmente, en el trabajo tenemos una arquitectura en capas, donde el código en la capa de presentación es realmente solo para organizar cosas para que se muestren en la interfaz de usuario. Este tipo de código no está cubierto con pruebas unitarias. Todo el resto de la lógica reside en la capa empresarial, que no tiene que tener ninguna dependencia en la capa de presentación (es decir, referencias específicas de UI como HttpContext) ya que la UI también puede ser una aplicación WinForms y no necesariamente una aplicación web .

De esta forma puede evitar perder el tiempo con frameworks simulados, tratando de simular HttpRequests, etc. ... aunque a menudo puede ser necesario.

3

En general para las pruebas de unidades ASP.NET, en lugar de acceder a HttpContext.Current debe tener una propiedad de tipo HttpContextBase cuyo valor se establece mediante inyección de dependencia (como en la respuesta proporcionada por Womp).

Sin embargo, para probar las funciones relacionadas con la seguridad, recomendaría utilizar Thread.CurrentThread.Principal (en lugar de HttpContext.Current.User). El uso de Thread.CurrentThread tiene la ventaja de ser también reutilizable fuera de un contexto web (y funciona igual en un contexto web porque el marco ASP.NET siempre establece ambos valores de la misma manera).

A continuación, prueba de Thread.CurrentThread.Principal que suelen utilizar una clase que establece el alcance Thread.currentThread a un valor de prueba y luego se restablece en disponer:

using (new UserResetScope("LOONEYTUNES\BUGSBUNNY")) { 
    // Put test here -- CurrentThread.Principal is reset when PrincipalScope is disposed 
} 

Esto encaja bien con la norma de seguridad de .NET componente - donde un componente tiene una interfaz conocida (IPrincipal) y una ubicación (Thread.CurrentThread.Principal) - y funcionará con cualquier código que use/compruebe correctamente contra Thread.CurrentThread.Principal.

Una clase alcance de base sería algo así como lo siguiente (los ajustes necesarios para cosas como papeles adición):

class UserResetScope : IDisposable { 
    private IPrincipal originalUser; 
    public UserResetScope(string newUserName) { 
     originalUser = Thread.CurrentPrincipal; 
     var newUser = new GenericPrincipal(new GenericIdentity(newUserName), new string[0]); 
     Thread.CurrentPrincipal = newUser; 
    } 
    public IPrincipal OriginalUser { get { return this.originalUser; } } 
    public void Dispose() { 
     Dispose(true); 
     GC.SuppressFinalize(this); 
    } 
    protected virtual void Dispose(bool disposing) { 
     if (disposing) { 
      Thread.CurrentPrincipal = originalUser; 
     } 
    } 
} 

Otra alternativa es, en lugar de utilizar la ubicación de los componentes de seguridad estándar, escriba su aplicación para utilizar detalles de seguridad inyectados, por ejemplo agregue una propiedad ISecurityContext con un método GetCurrentUser() o similar, y luego úselo de manera consistente en toda la aplicación, pero si va a hacer esto en el contexto de una aplicación web, entonces también podría usar el contexto preinstalado. , HttpContextBase.

1

Tener un vistazo a este http://haacked.com/archive/2007/06/19/unit-tests-web-code-without-a-web-server-using-httpsimulator.aspx

Usando la clase httpSimulator, Usted será capaz de hacer pasar una HttpContext al controlador de

HttpSimulator sim = new HttpSimulator("/", @"C:\intepub\?") 
.SimulateRequest(new Uri("http://localhost:54331/FileHandler.ashx? 
ticket=" + myticket + "&fileName=" + path)); 

FileHandler fh = new FileHandler(); 
fh.ProcessRequest(HttpContext.Current); 

HttpSimulator poner en práctica lo que tenemos que obtener una instancia HttpContext. Entonces no necesita usar Moq aquí.

1
[TestInitialize] 
public void TestInit() 
{ 
    HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null)); 
} 

también le puede moq, como a continuación

var controllerContext = new Mock<ControllerContext>(); 
     controllerContext.SetupGet(p => p.HttpContext.Session["User"]).Returns(TestGetUser); 
     controllerContext.SetupGet(p => p.HttpContext.Request.Url).Returns(new Uri("http://web1.ml.loc")); 
Cuestiones relacionadas