asp.net mvc - ASP/NET MVC: ¿Controladores de prueba con sesiones? ¿Burlón?
asp.net-mvc unit-testing (7)
Leí algunas de las respuestas aquí sobre: probar vistas y controladores, y burlarse, pero todavía no puedo entender cómo probar un controlador ASP.NET MVC que lee y establece valores de sesión (o cualquier otra variable basada en el contexto). ¿Cómo proporciono un contexto (de sesión) para mis métodos de prueba? ¿Se está burlando de la respuesta? Alguien tiene ejemplos? Básicamente, me gustaría simular una sesión antes de llamar al método del controlador y hacer que el controlador use esa sesión. ¿Algunas ideas?
Como HttpContext es estático, utilizo Typemock Isolator para simularlo, Typemock también tiene un complemento creado para pruebas de unidades ASP.NET llamado Ivonna .
Con MVC RC 1, ControllerContext ajusta el HttpContext y lo expone como una propiedad. Esto hace que burlarse sea mucho más fácil. Para simular una variable de sesión con Moq, haga lo siguiente:
var controller = new HomeController();
var context = MockRepository.GenerateStub<ControllerContext>();
context.Expect(x => x.HttpContext.Session["MyKey"]).Return("MyValue");
controller.ControllerContext = context;
Ver la publicación de Scott Gu para más detalles.
Consulte la publicación de Stephen Walther sobre el contexto de Faking the Controller:
ASP.NET MVC Tip # 12 - Falsificar el contexto del controlador
[TestMethod]
public void TestSessionState()
{
// Create controller
var controller = new HomeController();
// Create fake Controller Context
var sessionItems = new SessionStateItemCollection();
sessionItems["item1"] = "wow!";
controller.ControllerContext = new FakeControllerContext(controller, sessionItems);
var result = controller.TestSession() as ViewResult;
// Assert
Assert.AreEqual("wow!", result.ViewData["item1"]);
// Assert
Assert.AreEqual("cool!", controller.HttpContext.Session["item2"]);
}
El marco ASP.NET MVC no es muy simulado (o más bien, requiere demasiada configuración para simular correctamente, y provoca demasiada fricción cuando se prueba, en mi humilde opinión) debido a su uso de clases base abstractas en lugar de interfaces. Hemos tenido la suerte de escribir abstracciones para el almacenamiento por solicitud y por sesión. Mantenemos esas abstracciones muy claras y nuestros controladores dependen de esas abstracciones para el almacenamiento por solicitud o por sesión.
Por ejemplo, así es como administramos los formularios auth stuff. Tenemos un ISecurityContext:
public interface ISecurityContext
{
bool IsAuthenticated { get; }
IIdentity CurrentIdentity { get; }
IPrincipal CurrentUser { get; set; }
}
Con una implementación concreta como:
public class SecurityContext : ISecurityContext
{
private readonly HttpContext _context;
public SecurityContext()
{
_context = HttpContext.Current;
}
public bool IsAuthenticated
{
get { return _context.Request.IsAuthenticated; }
}
public IIdentity CurrentIdentity
{
get { return _context.User.Identity; }
}
public IPrincipal CurrentUser
{
get { return _context.User; }
set { _context.User = value; }
}
}
Me pareció burlarse de ser bastante fácil. Aquí hay un ejemplo de burlarse de la httpContextbase (que contiene la solicitud, la sesión y los objetos de respuesta) usando moq.
[TestMethod]
public void HowTo_CheckSession_With_TennisApp() {
var request = new Mock<HttpRequestBase>();
request.Expect(r => r.HttpMethod).Returns("GET");
var httpContext = new Mock<HttpContextBase>();
var session = new Mock<HttpSessionStateBase>();
httpContext.Expect(c => c.Request).Returns(request.Object);
httpContext.Expect(c => c.Session).Returns(session.Object);
session.Expect(c => c.Add("test", "something here"));
var playerController = new NewPlayerSignupController();
memberController.ControllerContext = new ControllerContext(new RequestContext(httpContext.Object, new RouteData()), playerController);
session.VerifyAll(); // function is trying to add the desired item to the session in the constructor
//TODO: Add Assertions
}
Espero que ayude.
Scott Hanselman tiene una publicación sobre cómo crear una aplicación rápida de carga de archivos con MVC y discute sobre el "frenesí" y específicamente sobre "Cómo burlarse de cosas que no son amigables".
Utilicé la siguiente solución: crear un controlador del que todos mis otros controladores hereden.
public class TestableController : Controller
{
public new HttpSessionStateBase Session
{
get
{
if (session == null)
{
session = base.Session ?? new CustomSession();
}
return session;
}
}
private HttpSessionStateBase session;
public class CustomSession : HttpSessionStateBase
{
private readonly Dictionary<string, object> dictionary;
public CustomSession()
{
dictionary = new Dictionary<string, object>();
}
public override object this[string name]
{
get
{
if (dictionary.ContainsKey(name))
{
return dictionary[name];
} else
{
return null;
}
}
set
{
if (!dictionary.ContainsKey(name))
{
dictionary.Add(name, value);
}
else
{
dictionary[name] = value;
}
}
}
//TODO: implement other methods here as needed to forefil the needs of the Session object. the above implementation was fine for my needs.
}
}
Luego usa el código de la siguiente manera:
public class MyController : TestableController { }