web-dev-qa-db-fra.com

MOQ: Unité teste une méthode en s'appuyant sur httpcontext

Considérons une méthode dans un assembly .NET:

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

J'aimerais appeler cette méthode à partir d'un test d'unité à l'aide du cadre MOQ. Cet assemblage fait partie d'une solution WebForms. Le test de l'unité ressemble à cela, mais il manque le code MOQ.

//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 objet httpcontext avec une certaine valeur comme "Mydomain\Myuser"?
  • Comment puis-je associer ce faux avec mon appel dans ma méthode statique à MyIdentityBL.GetSecurityContextUserName()?
  • Avez-vous des suggestions sur la manière d'améliorer ce code/architecture?
38
p.campbell

Webforms est notoirement intensif pour cette raison exacte - beaucoup de code peut compter sur des classes statiques dans le pipeline ASP.NET.

Afin de tester cela avec MOQ, vous devez refactoriser votre méthode GetSecurityContextUserName() _ méthode pour utiliser l'injection de dépendance avec un objet HttpContextBase.

HttpContextWrapper réside dans System.Web.Abstractions, qui est livré avec .NET 3.5. C'est une enveloppe pour la classe HttpContext et s'étend HttpContextBase, et vous pouvez construire un HttpContextWrapper comme ceci:

var wrapper = new HttpContextWrapper(HttpContext.Current);

Encore mieux, vous pouvez vous moquer d'une httpcontextbase et de configurer vos attentes à l'aide de MOQ. Y compris l'utilisateur connecté, etc.

var mockContext = new Mock<HttpContextBase>();

Avec cela en place, vous pouvez appeler GetSecurityContextUserName(mockContext.Object) et votre application est beaucoup moins couplée au site Web Static WebForms httpContext. Si vous allez faire beaucoup de tests qui s'appuient sur un contexte moqué, je suggère fortement regarder la classe MVCMockHelPers de Scott Hanselman , qui a une version à utiliser avec MOQ. Il gère facilement beaucoup de la configuration nécessaire. Et malgré le nom, vous n'avez pas besoin de le faire avec MVC - je l'utilise avec succès avec WebForms applications lorsque je peux les refracter pour utiliser HttpContextBase.

43
womp

En général pour les tests de l'unité ASP.NET, plutôt que d'accéder à httpcontext.Current, vous devez disposer d'une propriété de type httpContextBase dont la valeur est définie par injection de dépendance (telle que dans la réponse fournie par WOMP).

Toutefois, pour tester des fonctions liées à la sécurité, je recommanderais d'utiliser Thread.CurrentThread.Principal (au lieu de httpcontext.current.utiliser). Utilisation de thread.CurrentThread a l'avantage d'être également réutilisable en dehors d'un contexte Web (et fonctionne de la même manière dans un contexte Web, car le framework ASP.NET définit toujours les deux valeurs identiques).

Pour ensuite tester le fil.CurrentThread.Principal, j'utilise généralement une classe d'étendue qui définit le fil.CurrentThread à une valeur de test puis réinitialise lors de l'élimination:

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

Cela convient parfaitement à la composante Security standard .NET - où un composant a une interface connue (iprincipal) et une localisation (thread.currentThread.principal) - et fonctionnera avec n'importe quel code qui utilise/vérifie correctement contre le fil.CurrentThread.PRINCIPAL .

Une classe d'étendue de base 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 que, au lieu d'utiliser l'emplacement de composant de sécurité standard, écrivez votre application pour utiliser les détails de sécurité injectés, par ex. Ajoutez une propriété IsecurityContext avec une méthode getcurrentuser () ou similaire, puis utilisez-la constamment sur votre application - mais si vous allez le faire dans le contexte d'une application Web, vous pouvez également utiliser le contexte injecté pré-construit. , Httpcontextbase.

3
Sly Gryphon

Jetez un coup d'œil à cela http://haacked.com/archive/2007/06/19/UNIT-TESTS-WEB-Code-without-A-Web-Server-uTout-httpsimulator.aspx

Utilisation de la classe HTTPSIMulator, vous pourrez 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 que nous devons obtenir une instance httpcontext. Donc, vous n'avez pas besoin d'utiliser MOQ ici.

1
Davut Gürbüz
[TestInitialize]
public void TestInit()
{
  HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
}

Aussi vous pouvez le 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"));
1
PUG

Si vous utilisez le modèle de sécurité CLR (comme nous le faisons), vous devez utiliser certaines fonctions abstraites pour obtenir et définir le principal courant si vous souhaitez autoriser des tests et les utiliser chaque fois que vous obtenez ou définissez le principal. Cela vous permet d'obtenir/définir le principal où est pertinent (généralement sur HttpContext sur le Web et sur le fil actuel ailleurs comme des 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 un directeur personnalisé, ceux-ci peuvent être assez bien intégrés à son interface, par exemple en dessous de Current _ GetCurrentPrincipal et SetAsCurrentSetCurrentPrincipal.

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

Cela n'est pas vraiment lié à l'utilisation de MOQ pour un test d'unité de ce dont vous avez besoin.

En règle générale, nous avons une architecture en couches, où le code sur la couche de présentation est vraiment juste pour organiser des choses pour être affichées sur l'interface utilisateur. Ce type de code n'est pas couvert d'essais unitaires. Tout le reste de la logique réside sur la couche d'entreprise, qui ne doit pas nécessairement avoir une dépendance sur la couche de présentation (par exemple, des références spécifiques à l'UI, telle que le HTTPContext), car l'interface utilisateur peut également être une application WinForms et non nécessairement une application Web. .

De cette façon, vous pouvez éviter de gâcher avec des cadres de simulation, d'essayer de simuler HTTPequests, etc. Bien que cela puisse toujours être nécessaire.

0
Juri