Question

Considérons une méthode dans un assemblage .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 unitaire utilisant le framework Moq. Cet assemblage fait partie d'une solution de formulaires Web. Le test unitaire ressemble à ceci, mais le code Moq me manque.

//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 valeur telle que "MyDomain \ MyUser"?
  • Comment associer ce faux à mon appel dans ma méthode statique à MyIdentityBL.GetSecurityContextUserName () ?
  • Avez-vous des suggestions pour améliorer ce code / cette architecture?
Était-ce utile?

La solution

Les formulaires Web sont notoirement indiscutables pour cette raison précise - beaucoup de code peut s’appuyer sur des classes statiques dans le pipeline asp.net.

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

HttpContextWrapper réside dans System.Web.Abstractions , qui est fourni avec .Net 3.5. C'est un wrapper pour la classe HttpContext et étend HttpContextBase , et vous pouvez construire un HttpContextWrapper comme ceci:

var wrapper = new HttpContextWrapper(HttpContext.Current);

Encore mieux, vous pouvez vous moquer d’une base HttpContextBase et définir vos attentes à l’aide de Moq. Y compris l'utilisateur connecté, etc.

var mockContext = new Mock<HttpContextBase>();

Avec ceci en place, vous pouvez appeler GetSecurityContextUserName (mockContext.Object) , et votre application est beaucoup moins couplée au WebForms HttpContext statique. Si vous envisagez de nombreux tests basés sur un contexte simulé, je vous suggère vivement de prendre un aperçu de la classe MvcMockHelpers de Scott Hanselman , qui possède une version à utiliser avec Moq. Il gère facilement une grande partie 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 les applications webforms lorsque je peux les reformater pour utiliser HttpContextBase .

Autres conseils

En général, pour les tests unitaires ASP.NET, plutôt que d'accéder à HttpContext.Current, vous devez 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 tester les fonctions liées à la sécurité, je vous recommande d’utiliser Thread.CurrentThread.Principal (au lieu de HttpContext.Current.User). L’utilisation de Thread.CurrentThread présente l’avantage d’être réutilisable en dehors du contexte Web (et fonctionne de la même manière dans un contexte Web, car le cadre ASP.NET définit toujours les deux valeurs de la même manière).

Pour tester ensuite Thread.CurrentThread.Principal, j’utilise généralement une classe de portée qui définit Thread.CurrentThread sur une valeur de test, puis le réinitialise lors de la suppression:

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

Cela correspond bien au composant de sécurité .NET standard - lorsqu'un composant possède une interface connue (IPrincipal) et un emplacement (Thread.CurrentThread.Principal) - et fonctionne avec tout code qui utilise / vérifie correctement les threads. CurrentThread.Principal.

Une classe de portée de base ressemblerait à ce qui suit (ajustez si nécessaire pour ajouter des rôles, par exemple):

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

Au lieu d'utiliser l'emplacement du composant de sécurité standard, vous pouvez également écrire dans votre application pour qu'elle utilise les détails de sécurité injectés, par exemple. ajoutez une propriété ISecurityContext avec une méthode GetCurrentUser () ou similaire, puis utilisez-la de manière cohérente dans l'ensemble de votre application. Toutefois, si vous envisagez de le faire dans le contexte d'une application Web, vous pouvez également utiliser le contexte injecté pré-construit. , HttpContextBase.

Regardez ça 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. Donc, vous n'avez pas besoin d'utiliser Moq ici.

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

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

Si vous utilisez le modèle de sécurité CLR (comme nous le faisons), vous devrez utiliser certaines fonctions abstraites pour obtenir et définir le principal actuel si vous souhaitez autoriser les tests, et les utiliser chaque fois que vous obtenez ou définissez le principal. . Cela vous permet d’obtenir / de définir le principal là où il est pertinent (généralement sur HttpContext sur le Web et sur le thread en cours 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 un principal personnalisé, il peut être assez bien intégré à son interface. Par exemple, Current appelle GetCurrentPrincipal et SetAsCurrent . appellerait SetCurrentPrincipal .

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

Cela n’est pas vraiment lié à l’utilisation de Moq pour le test unitaire de ce dont vous avez besoin.

Généralement, nous avons au travail une architecture en couches, où le code de la couche de présentation sert uniquement à organiser les éléments à afficher sur l'interface utilisateur. Ce type de code n'est pas couvert par les tests unitaires. Tout le reste de la logique réside dans la couche de gestion, ce qui ne nécessite aucune dépendance vis-à-vis de la couche de présentation (références spécifiques à une interface utilisateur telles que HttpContext), l'interface utilisateur pouvant également être une application WinForms et pas nécessairement une application Web. .

De cette manière, vous éviterez de perdre votre temps avec les frameworks Mock, en essayant de simuler HttpRequests, etc., bien que cela reste souvent nécessaire.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top