c# - verifiable - verify moq
Moq: unidad probando un método que depende de HttpContext (6)
Considere un método en un ensamblado .NET:
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 prueba unitaria 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 falso con algún valor como ''MyDomain / MyUser''?
- ¿Cómo asocio ese falso con mi llamada a mi método estático en
MyIdentityBL.GetSecurityContextUserName()
? - ¿Tiene alguna sugerencia sobre cómo mejorar este código / arquitectura?
Echa un vistazo a este http://haacked.com/archive/2007/06/19/unit-tests-web-code-without-a-web-server-using-httpsimulator.aspx
Al usar la clase httpSimulator, podrás pasar un HttpContext al controlador
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 implementa lo que necesitamos para obtener una instancia HttpContext. Entonces no necesita usar Moq aquí.
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 la inyección de dependencia (como en la respuesta proporcionada por Womp).
Sin embargo, para probar las funciones relacionadas con la seguridad, recomendaría usar 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).
Para probar Thread.CurrentThread.Principal, suelo usar una clase de ámbito que establece Thread.CurrentThread en un valor de prueba y luego se restablece al deshacerse:
using (new UserResetScope("LOONEYTUNES/BUGSBUNNY")) {
// Put test here -- CurrentThread.Principal is reset when PrincipalScope is disposed
}
Esto encaja bien con el componente de seguridad .NET estándar, 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 de alcance base sería algo como lo siguiente (ajústelo según sea necesario para cosas como agregar roles):
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 que, en lugar de usar la ubicación del componente de seguridad estándar, escriba su aplicación para usar los detalles de seguridad inyectados, por ejemplo, agregue una propiedad ISecurityContext con un método GetCurrentUser () o similar, y luego utilícelo de manera consistente en toda la aplicación, pero si yendo a hacer esto en el contexto de una aplicación web, entonces también podría usar el contexto inyectado preconfigurado, HttpContextBase.
Esto no está realmente relacionado con el uso de Moq para las pruebas unitarias de lo que necesita.
En general, 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, puedes evitar jugar con frameworks de Mock, intentar simular HttpRequests, etc. aunque a veces puede ser necesario.
Si está utilizando el modelo de seguridad CLR (como lo hacemos nosotros), entonces necesitará usar algunas funciones abstractas para obtener y establecer el principal actual si desea permitir las pruebas, y usarlas siempre que consiga o establezca el principal. Hacer esto le permite obtener / configurar el principal donde sea relevante (normalmente en HttpContext
en la web y en el hilo actual en otros lugares, como las pruebas unitarias). Esto se vería algo así 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, estos pueden integrarse bastante bien en su interfaz, por ejemplo, a continuación, Current
llamaría GetCurrentPrincipal
y SetAsCurrent
llamaría a SetCurrentPrincipal
.
public class MyCustomPrincipal : IPrincipal
{
public MyCustomPrincipal Current { get; }
public bool HasCurrent { get; }
public void SetAsCurrent();
}
Webforms 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 incluye con .Net 3.5. Es un contenedor para la clase HttpContext
y amplía HttpContextBase
, y puedes construir un HttpContextWrapper
siguiente manera:
var wrapper = new HttpContextWrapper(HttpContext.Current);
Mejor aún, puede simular una HttpContextBase y configurar sus expectativas con Moq. Incluyendo el usuario conectado, etc.
var mockContext = new Mock<HttpContextBase>();
Con esto en su lugar, puede llamar a GetSecurityContextUserName(mockContext.Object)
, y su aplicación está mucho menos acoplada al WebForms estático HttpContext. Si vas a hacer muchas pruebas que se basan en un contexto burlado, te sugiero echar un vistazo a la clase MvcMockHelpers de Scott Hanselman , 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
.
[TestInitialize]
public void TestInit()
{
HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
}
También puedes 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"));