2010-12-07 5 views
143

J'essaie d'ajouter des tests unitaires à une application ASP.NET MVC que j'ai créée. Dans mes tests unitaires J'utilise le code suivant:Mock HttpContext.Current dans Test Init, méthode

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

    ... 
} 

Avec les aides suivantes pour se moquer du 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 a les éléments suivants:

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

dans cette méthode, il appelle une bibliothèque (que je ne contrôle pas) qui tente d'exécuter le code suivant:

HttpContext.Current.User.Identity.IsAuthenticated 

Maintenant, vous pouvez probablement voir le problème. J'ai mis le faux HttpContext contre le contrôleur mais pas dans cette méthode Init de base. Les tests unitaires/moqueurs sont très nouveaux pour moi, je veux donc m'assurer de bien comprendre. Quelle est la manière correcte pour moi de maquiller le HttpContext afin qu'il soit partagé sur mon contrôleur et toutes les bibliothèques qui sont appelées dans ma méthode Init.

Répondre

297

HttpContext.Current renvoie une instance de System.Web.HttpContext, qui ne s'étend pas System.Web.HttpContextBase. HttpContextBase a été ajouté plus tard à l'adresse HttpContext étant difficile à se moquer. Les deux classes sont fondamentalement non liées (HttpContextWrapper est utilisé comme un adaptateur entre eux).

Heureusement, HttpContext lui-même est fakeable juste assez pour vous remplacer le IPrincipal (utilisateur) et IIdentity.

Le code suivant fonctionne 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

Vive mais comment J'ai mis ceci pour un utilisateur déconnecté? – nfplee

+5

@nfplee - Si vous passez une chaîne vide dans le constructeur 'GenericIdentity',' IsAuthenticated' retournera false –

+2

Est-ce que cela pourrait être utilisé pour simuler Cache dans le HttpContext? – DevDave

24

Ci-dessous test Init aussi faire le travail.

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

Je ne peux pas obtenir l'adressabilité à HTTPContext dans un projet de test distinct dans ma solution. Êtes-vous capable de l'obtenir via l'héritage d'un contrôleur? –

+1

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

+0

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? –

1

Si votre application tierce partie redirect interne, il est donc préférable de se moquer HttpContext de manière ci-dessous:

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

Je sais que c'est un sujet plus, Mocking cependant 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 Mocking une application MVC 3 en utilisant Moq 4 après la mise à niveau vers Visual Studio 2013. Aucun des tests unitaires ne fonctionnait en mode débogage et le HttpContext affichait "Impossible d'évaluer l'expression" en essayant de Jetez un œil aux variables.

Il s'avère que Visual Studio 2013 a des problèmes d'évaluation de certains objets. Pour que le débogage des applications Web mockées fonctionne à nouveau, j'ai dû cocher la case "Use Managed Compatibility Mode" dans Tools => Options => Debugging => Paramètres généraux.

je fais généralement quelque chose comme ceci:

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 d'initier le contexte comme celui-ci

FakeHttpContext.SetFakeContext(moController); 

et appelant la méthode dans le contrôleur droit devant

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

Y a-t-il une bonne raison de la définir ainsi par rapport à la réponse acceptée? D'emblée, cela semble plus compliqué et ne semble pas offrir d'avantage supplémentaire. – kilkfoe

Questions connexes