43 votes

Moq: test unitaire d'une méthode reposant sur HttpContext

Envisager une méthode dans un .NET de l'assemblée:

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

Je tiens à appeler cette méthode à partir d'un test de l'unité à l'aide de l'Moq cadre. Cette assemblée est une partie d'un webforms solution. L'unité de test ressemble à ça, mais je suis absent de l'Moq code.

//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.");

Question:

  • Comment puis-je utiliser Moq pour organiser un faux HttpContext objet avec une valeur comme "MyDomain\MyUser'?
  • Comment puis-je l'associer faux à mon appel, dans ma méthode statique à l' MyIdentityBL.GetSecurityContextUserName()?
  • Avez-vous des suggestions sur la façon d'améliorer ce code, de l'architecture?

47voto

womp Points 71924

Webforms est notoirement impossible à vérifier pour cette exacte raison - beaucoup de code peut s'appuyer sur les classes statiques dans le asp.net pipeline.

Afin de tester cette Moq, vous avez besoin de revoir votre GetSecurityContextUserName() méthode à utiliser l'injection de dépendance avec un HttpContextBase objet.

HttpContextWrapper réside en System.Web.Abstractions, qui est livré avec .Net 3.5. C'est un wrapper pour l' HttpContext classe, et s'étend HttpContextBase, et vous pouvez construire une HttpContextWrapper comme cela:

var wrapper = new HttpContextWrapper(HttpContext.Current);

Encore mieux, vous pouvez simuler un HttpContextBase et de définir vos attentes sur elle à l'aide de Moq. Y compris l'utilisateur connecté, etc.

var mockContext = new Mock<HttpContextBase>();

Dans ce lieu, vous pouvez appeler GetSecurityContextUserName(mockContext.Object), et votre application est beaucoup moins couplée à la statique WebForms HttpContext. Si vous allez faire beaucoup de tests qui reposent sur un moqué de contexte, je vous suggère fortement de prendre un coup d'oeil à Scott, Hanselman de MvcMockHelpers classe, qui dispose d'une version pour une utilisation avec Moq. Commodément gère beaucoup de la configuration nécessaire. Et malgré le nom, vous n'avez pas besoin de le faire avec MVC - je l'utiliser avec succès avec les formulaires web apps quand je peux refactoriser utiliser HttpContextBase.

4voto

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

Vous pouvez également moq comme ci-dessous

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

3voto

Sly Points 2992

En général, pour ASP.NET les tests unitaires, plutôt que d'accéder à HttpContext.Actuelle, vous devriez avoir une propriété de type HttpContextBase dont la valeur est définie par l'injection de dépendance (comme dans la réponse fournie par Womp).

Cependant, pour le test des fonctions de sécurité je vous conseille d'utiliser du Fil.CurrentThread.Principal (au lieu de HttpContext.Actuel.De l'utilisateur). À L'Aide De Fil.CurrentThread a l'avantage de pouvoir être réutilisable en dehors d'un contexte web (et fonctionne de la même dans un contexte web, parce que le ASP.NET cadre définit toujours les deux valeurs de même).

Alors test Thread.CurrentThread.Principal j'ai l'habitude d'utiliser un champ de la classe qui définit le Fil.CurrentThread à un test de valeur et puis réinitialise à éliminer:

using (new UserResetScope("LOONEYTUNES\BUGSBUNNY")) {
    // Put test here -- CurrentThread.Principal is reset when PrincipalScope is disposed
}

Cela correspond bien avec le standard .NET composant de sécurité -- où un composant a une interface connue (IPrincipal) et l'emplacement (Thread.CurrentThread.Principal) -- et fonctionne avec n'importe quel code qui utilise correctement/les contrôles contre le Thread.CurrentThread.Principal.

Une base de portée de la classe serait quelque chose comme ce qui suit (ajuster si nécessaire pour des choses comme l'ajout de rôles):

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

Une autre alternative est, au lieu d'utiliser la norme de sécurité de l'emplacement du composant, écrivez votre application pour utiliser injecté détails de sécurité, par exemple, ajouter un ISecurityContext propriété avec un GetCurrentUser() la méthode ou similaire, et ensuite l'utiliser de façon constante tout au long de votre application, mais si vous allez faire cela dans le contexte d'une application web, alors vous pourriez aussi bien utiliser la pré-construit injecté contexte, HttpContextBase.

1voto

Davut Gürbüz Points 1609

Jetez un œil à ce http://haacked.com/archive/2007/06/19/unit-tests-web-code-without-a-web-server-using-httpsimulator.aspx

En utilisant la classe httpSimulator, vous pourrez faire passer un HttpContext au gestionnaire

             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 implémente ce dont nous avons besoin pour obtenir une instance HttpContext. Vous n'avez donc pas besoin d'utiliser Moq ici.

0voto

Greg Beech Points 55270

Si vous utilisez le modèle de sécurité CLR (comme nous), alors vous aurez besoin d'utiliser certaines fonctions abstraites d'obtenir et de définir le courant principal si vous souhaitez permettre de tester et d'utiliser à chaque fois que l'obtention ou le réglage de la principale. Cela vous permet d'obtenir/définir les principaux partout où cela est pertinent (généralement sur HttpContext sur le web, et sur le thread en cours d'ailleurs comme les tests unitaires). Cela ressemblerait à quelque chose comme:

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 vous utilisez une entité personnalisée, alors elles peuvent être assez joliment intégré dans son interface, pour l'exemple ci-dessous, Current appellerais GetCurrentPrincipal et SetAsCurrent appellerais SetCurrentPrincipal.

public class MyCustomPrincipal : IPrincipal
{
    public MyCustomPrincipal Current { get; }
    public bool HasCurrent { get; }
    public void SetAsCurrent();
}

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