Simulacros de HttpContext.Current en Test Init Method

177

Estoy tratando de agregar pruebas unitarias a una aplicación ASP.NET MVC que he construido. En mis pruebas unitarias utilizo el siguiente código:

[TestMethod]
public void IndexAction_Should_Return_View() {
    var controller = new MembershipController();
    controller.SetFakeControllerContext("TestUser");

    ...
}

Con los siguientes ayudantes para burlarse del contexto del controlador:

public static class FakeControllerContext {
    public static HttpContextBase FakeHttpContext(string username) {
        var context = new Mock<HttpContextBase>();

        context.SetupGet(ctx => ctx.Request.IsAuthenticated).Returns(!string.IsNullOrEmpty(username));

        if (!string.IsNullOrEmpty(username))
            context.SetupGet(ctx => ctx.User.Identity).Returns(FakeIdentity.CreateIdentity(username));

        return context.Object;
    }

    public static void SetFakeControllerContext(this Controller controller, string username = null) {
        var httpContext = FakeHttpContext(username);
        var context = new ControllerContext(new RequestContext(httpContext, new RouteData()), controller);
        controller.ControllerContext = context;
    }
}

Esta clase de prueba hereda de una clase base que tiene lo siguiente:

[TestInitialize]
public void Init() {
    ...
}

Dentro de este método, llama a una biblioteca (sobre la que no tengo control) que intenta ejecutar el siguiente código:

HttpContext.Current.User.Identity.IsAuthenticated

Ahora probablemente puedas ver el problema. He configurado el HttpContext falso contra el controlador, pero no en este método básico de Init. La prueba / burla de la unidad es muy nueva para mí, así que quiero asegurarme de hacerlo bien. ¿Cuál es la forma correcta para mí de simular el HttpContext para que se comparta entre mi controlador y cualquier biblioteca que se llame en mi método Init?

nfplee
fuente

Respuestas:

362

HttpContext.Currentdevuelve una instancia de System.Web.HttpContext, que no se extiende System.Web.HttpContextBase. HttpContextBasefue agregado más tarde para abordar HttpContextser difícil de burlar. Las dos clases son básicamente no relacionadas (HttpContextWrapper se usa como un adaptador entre ellas).

Afortunadamente, HttpContextes falso lo suficiente como para reemplazar el IPrincipal(Usuario) yIIdentity .

El siguiente código se ejecuta como se esperaba, incluso en una aplicación de consola:

HttpContext.Current = new HttpContext(
    new HttpRequest("", "http://tempuri.org", ""),
    new HttpResponse(new StringWriter())
    );

// User is logged in
HttpContext.Current.User = new GenericPrincipal(
    new GenericIdentity("username"),
    new string[0]
    );

// User is logged out
HttpContext.Current.User = new GenericPrincipal(
    new GenericIdentity(String.Empty),
    new string[0]
    );
Richard Szalay
fuente
Saludos, pero ¿cómo podría configurar esto para un usuario desconectado?
nfplee
55
@nfplee - Si pasa una cadena vacía al GenericIdentityconstructor, IsAuthenticateddevolverá falso
Richard Szalay
2
¿Podría esto usarse para burlarse de Cache en el HttpContext?
DevDave
1
Si, podria. ¡Gracias!
DevDave
44
@CiaranG: usos de MVC HttpContextBase, que se pueden burlar. No es necesario utilizar la solución alternativa que publiqué si está utilizando MVC. Si continúas con esto, probablemente necesites ejecutar el código que publiqué antes de crear el controlador.
Richard Szalay
35

Debajo de Test Init también hará el trabajo.

[TestInitialize]
public void TestInit()
{
  HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
  YourControllerToBeTestedController = GetYourToBeTestedController();
}
DOGUILLO
fuente
No puedo obtener direccionamiento a HTTPContext en un proyecto de prueba separado en mi solución. ¿Puede obtenerlo heredando un controlador?
John Peters
1
¿Tiene referencia System.Weben su proyecto de prueba?
PUG
Sí, pero mi proyecto es un proyecto MVC, ¿es posible que la versión MVC de System.Web solo contenga un subconjunto de ese espacio de nombres?
John Peters
2
@ user1522548 (debe crear una cuenta) Assembly System.Web.dll, v4.0.0.0 definitivamente tiene HTTPContext, acabo de comprobar mi código fuente.
PUG
Mi error, tengo una referencia en el archivo a System.Web.MVC y NO System.Web. Gracias por tu ayuda.
John Peters
7

Sé que este es un tema más antiguo, sin embargo, burlarnos de una aplicación MVC para pruebas unitarias es algo que hacemos de manera muy regular.

Solo quería agregar mis experiencias Burlándose de una aplicación MVC 3 usando Moq 4 después de actualizar a Visual Studio 2013. Ninguna de las pruebas unitarias funcionaba en modo de depuración y el HttpContext mostraba "no se pudo evaluar la expresión" cuando se trataba de mirar las variables .

Resulta que Visual Studio 2013 tiene problemas para evaluar algunos objetos. Para que la depuración de aplicaciones web simuladas volviera a funcionar, tuve que marcar el "Usar el modo de compatibilidad administrada" en Herramientas => Opciones => Depuración => Configuración general.

Generalmente hago algo como esto:

public static class FakeHttpContext
{
    public static void SetFakeContext(this Controller controller)
    {

        var httpContext = MakeFakeContext();
        ControllerContext context =
        new ControllerContext(
        new RequestContext(httpContext,
        new RouteData()), controller);
        controller.ControllerContext = context;
    }


    private static HttpContextBase MakeFakeContext()
    {
        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(c=> c.Request).Returns(request.Object);
        context.Setup(c=> c.Response).Returns(response.Object);
        context.Setup(c=> c.Session).Returns(session.Object);
        context.Setup(c=> c.Server).Returns(server.Object);
        context.Setup(c=> c.User).Returns(user.Object);
        user.Setup(c=> c.Identity).Returns(identity.Object);
        identity.Setup(i => i.IsAuthenticated).Returns(true);
        identity.Setup(i => i.Name).Returns("admin");

        return context.Object;
    }


}

E iniciando el contexto así

FakeHttpContext.SetFakeContext(moController);

Y llamando al Método en el controlador directamente

long lReportStatusID = -1;
var result = moController.CancelReport(lReportStatusID);
aggaton
fuente
¿Hay una buena razón para establecerlo de esta manera frente a la respuesta aceptada? De buenas a primeras, esto parece más complicado y no parece ofrecer ningún beneficio adicional.
kilkfoe
1
Ofrece un método de burla más detallado / modular
Vincent Buscarello
4

Si el tercero de su aplicación redirige internamente, es mejor burlarse de HttpContext de la siguiente manera:

HttpWorkerRequest initWorkerRequest = new SimpleWorkerRequest("","","","",new StringWriter(CultureInfo.InvariantCulture));
System.Web.HttpContext.Current = new HttpContext(initWorkerRequest);
System.Web.HttpContext.Current.Request.Browser = new HttpBrowserCapabilities();
System.Web.HttpContext.Current.Request.Browser.Capabilities = new Dictionary<string, string> { { "requiresPostRedirectionHandling", "false" } };
Divang
fuente