195 votes

Mock HttpContext.Current dans la méthode Init du test

J'essaie d'ajouter des tests unitaires à une application ASP.NET MVC que j'ai construite. Dans mes tests unitaires, j'utilise le code suivant :

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

    ...
}

Avec les aides suivantes pour simuler le contexte du contrôleur :

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

Cette classe de test hérite d'une classe de base qui présente les caractéristiques suivantes :

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

Cette méthode appelle une bibliothèque (sur laquelle je n'ai aucun contrôle) qui tente d'exécuter le code suivant :

HttpContext.Current.User.Identity.IsAuthenticated

Maintenant, vous pouvez probablement voir le problème. J'ai défini le faux HttpContext dans le contrôleur mais pas dans la méthode de base Init. Les tests unitaires et le mocking sont très nouveaux pour moi, je veux donc être sûr de bien faire les choses. Quelle est la manière correcte de simuler le HttpContext de sorte qu'il soit partagé par mon contrôleur et toutes les bibliothèques appelées dans ma méthode Init.

391voto

Richard Szalay Points 42486

HttpContext.Current renvoie une instance de System.Web.HttpContext qui ne s'étend pas System.Web.HttpContextBase . HttpContextBase a été ajouté ultérieurement pour tenir compte de la HttpContext être difficile à moquer. Les deux classes sont fondamentalement sans rapport ( HttpContextWrapper est utilisé comme adaptateur entre eux).

Heureusement, HttpContext est lui-même falsifiable, juste assez pour que vous puissiez remplacer l'élément IPrincipal (Utilisateur) et IIdentity .

Le code suivant s'exécute comme prévu, même dans une application console :

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]
    );

0 votes

Merci, mais comment puis-je configurer cela pour un utilisateur déconnecté ?

5 votes

@nfplee - Si vous passez une chaîne vide dans la fonction GenericIdentity constructeur, IsAuthenticated retournera false

2 votes

Cela pourrait-il être utilisé pour simuler le cache dans le HttpContext ?

36voto

jaminator Points 843

Le test Init ci-dessous fera également l'affaire.

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

0 votes

Je n'arrive pas à obtenir l'adressabilité à HTTPContext dans un projet Test séparé dans ma solution. Pouvez-vous l'obtenir en héritant d'un contrôleur ?

1 votes

Avez-vous une référence à System.Web dans votre projet de test ?

0 votes

Oui mais mon projet est un projet MVC, est-il possible que la version MVC de System.Web ne contienne qu'un sous-ensemble de cet espace de noms ?

10voto

aggaton Points 183

Je sais qu'il s'agit d'un sujet ancien, mais la mise en place d'une application MVC pour les tests unitaires est quelque chose que nous faisons très régulièrement.

Je voulais juste ajouter mes expériences sur la mise en place d'une application MVC 3 avec Moq 4 après la mise à niveau vers Visual Studio 2013. Aucun des tests unitaires ne fonctionnait en mode de débogage et le HttpContext affichait "could not evaluate expression" lorsqu'on essayait de consulter les variables.

Il s'avère que Visual Studio 2013 a des problèmes pour évaluer certains objets. Pour que le débogage des applications web simulées fonctionne à nouveau, j'ai dû cocher la case "Utiliser le mode de compatibilité géré" dans Tools=>Options=>Debugging=>General settings.

Je fais généralement quelque chose comme ça :

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

}

Et en initiant le contexte comme ceci

FakeHttpContext.SetFakeContext(moController);

Et l'appel de la méthode dans le contrôleur est direct

long lReportStatusID = -1;
var result = moController.CancelReport(lReportStatusID);

4voto

Divang Points 51

Si votre application redirige des tiers en interne, il est préférable de simuler HttpContext de la manière suivante :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X