web-dev-qa-db-fra.com

Test unitaire de HttpContext.Current.Cache ou d'autres méthodes côté serveur en C #?

Lors de la création d'un test unitaire pour une classe utilisant la classe HttpContext.Current.Cache , j'obtiens une erreur lors de l'utilisation de NUnit. La fonctionnalité est basique - vérifiez si un élément est dans le cache, et sinon, créez-le et mettez-le dans:

if (HttpContext.Current.Cache["Some_Key"] == null) {
    myObject = new Object();
    HttpContext.Current.Cache.Insert("Some_Key", myObject);
}
else {
    myObject = HttpContext.Current.Cache.Get("Some_Key");
}

Lorsqu’il appelle à partir d’un test unitaire, il échoue avec à NullReferenceException lorsqu’il rencontre la première ligne Cache. En Java, je voudrais utiliser Cactus pour tester le code côté serveur. Existe-t-il un outil similaire que je peux utiliser pour le code C #? Cette SO question mentionne les faux cadres - est-ce la seule façon de tester ces méthodes? Existe-t-il un outil similaire pour exécuter des tests pour C #?

De plus, je ne vérifie pas si la variable Cache est nulle, car je ne souhaite pas écrire de code spécifiquement pour le test unitaire et suppose que celui-ci sera toujours valide lors de l'exécution sur un serveur. Est-ce valide ou dois-je ajouter des contrôles nuls autour du cache?

33
Tai Squared

Pour ce faire, il convient d’éviter l’utilisation directe de HttpContext ou d’autres classes similaires et de les remplacer par des mocs. Après tout, vous n’essayez pas de vérifier que le HttpContext fonctionne correctement (c’est le travail de Microsoft), vous essayez simplement de vérifier que les méthodes ont été appelées au bon moment.

Étapes (au cas où vous voudriez simplement connaître la technique sans fouiller dans des tonnes de blogs):

  1. Créez une interface qui décrit les méthodes que vous souhaitez utiliser dans votre mise en cache (probablement des éléments tels que GetItem, SetItem, ExpireItem). Appelez-le ICache ou ce que vous aimez

  2. Créer une classe qui implémente cette interface et transmet les méthodes au réel HttpContext

  3. Créez une classe qui implémente la même interface et agit simplement comme un cache factice. Il peut utiliser un dictionnaire ou quelque chose si vous vous souciez de sauver des objets

  4. Modifiez votre code d'origine afin qu'il n'utilise pas du tout HttpContext, mais utilise uniquement un ICache. Le code devra alors obtenir une instance de ICache - vous pouvez soit passer une instance dans le constructeur de votre classe (c'est tout ce que l'injection de dépendance est réellement), ou la coller dans une variable globale.

  5. Dans votre application de production, définissez le cache ICache comme étant votre véritable cache HttpContext-Backed-Backed, et dans vos tests unitaires, définissez le cache ICache comme le cache fictif.

  6. Profit!

42
Orion Edwards

Je conviens avec les autres que l’utilisation d’une interface serait la meilleure option, mais il est parfois impossible de modifier un système existant. Voici un code que je viens de compiler à partir d’un de mes projets et qui devrait vous donner les résultats que vous recherchez. C’est la solution la plus éloignée de jolie ou une excellente solution, mais si vous ne pouvez vraiment pas changer votre code, il devrait faire son travail.

using System;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Web;
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;

[TestFixture]
public class HttpContextCreation
{
    [Test]
    public void TestCache()
    {
        var context = CreateHttpContext("index.aspx", "http://tempuri.org/index.aspx", null);
        var result = RunInstanceMethod(Thread.CurrentThread, "GetIllogicalCallContext", new object[] { });
        SetPrivateInstanceFieldValue(result, "m_HostContext", context);

        Assert.That(HttpContext.Current.Cache["val"], Is.Null);

        HttpContext.Current.Cache["val"] = "testValue";
        Assert.That(HttpContext.Current.Cache["val"], Is.EqualTo("testValue"));
    }

    private static HttpContext CreateHttpContext(string fileName, string url, string queryString)
    {
        var sb = new StringBuilder();
        var sw = new StringWriter(sb);
        var hres = new HttpResponse(sw);
        var hreq = new HttpRequest(fileName, url, queryString);
        var httpc = new HttpContext(hreq, hres);
        return httpc;
    }

    private static object RunInstanceMethod(object source, string method, object[] objParams)
    {
        var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
        var type = source.GetType();
        var m = type.GetMethod(method, flags);
        if (m == null)
        {
            throw new ArgumentException(string.Format("There is no method '{0}' for type '{1}'.", method, type));
        }

        var objRet = m.Invoke(source, objParams);
        return objRet;
    }

    public static void SetPrivateInstanceFieldValue(object source, string memberName, object value)
    {
        var field = source.GetType().GetField(memberName, BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
        if (field == null)
        {
            throw new ArgumentException(string.Format("Could not find the private instance field '{0}'", memberName));
        }

        field.SetValue(source, value);
    }
}
29
Brian Surowiec
HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
16
Shawn Miller

Si vous utilisez .NET 3.5, vous pouvez utiliser System.Web.Abstractions dans votre application.

Justin Etheredge a un excellent post sur la façon de se moquer de HttpContext (qui contient la classe de cache).

Dans l'exemple de Justin, je passe une HttpContextBase à mes contrôleurs à l'aide de HttpContextFactory.GetHttpContext. Lorsque je me moque d'eux, je construis simplement un simulacre pour faire des appels à l'objet cache.

6
Steve Wright

Il existe une approche plus récente pour aider à traiter spécifiquement du cache dans les tests unitaires.

Je recommanderais d'utiliser la nouvelle approche MemoryCache.Default de Microsoft. Vous devrez utiliser .NET Framework 4.0 ou version ultérieure et inclure une référence à System.Runtime.Caching.

Voir l'article ici -> http://msdn.Microsoft.com/en-us/library/dd997357(v=vs.100).aspx

MemoryCache.Default fonctionne à la fois pour les applications Web et non Web. L'idée est donc de mettre à jour votre application Web pour supprimer les références à HttpContext.Current.Cache et les remplacer par des références à MemoryCache.Default. Plus tard, lorsque vous lancez la décision de tester unitairement ces mêmes méthodes, l'objet de cache est toujours disponible et ne sera pas nul. (Parce qu'il ne dépend pas d'un HttpContext.)

De cette façon, vous n'avez même pas nécessairement besoin de vous moquer du composant cache.

5
ClearCloud8

Un consensus général semble être que conduire tout ce qui est lié à HttpContext à partir d'un test unitaire est un cauchemar total et doit être évité si possible.

Je pense que vous êtes sur la bonne voie en ce qui concerne les moqueries. J'aime RhinoMocks ( http://ayende.com/projects/rhino-mocks.aspx ).

J'ai aussi lu de bonnes choses sur MoQ ( http://code.google.com/p/moq ), bien que je ne l'aie pas encore essayé.

Si vous voulez vraiment écrire des interfaces utilisateur Web testables en C #, la façon dont les gens semblent se diriger est d'utiliser le framework MVC ( http://www.asp.net/mvc ) plutôt que WebForms ...

2
Antony Perkov

Vous pouvez utiliser la classe HttpContextBase dans System.Web.Abstractions.dll. C'est une nouvelle DLL dans .NET 3.5.

Vous pouvez trouver un exemple d'utilisation dans le lien ci-dessous.

http://vkreynin.wordpress.com/2009/03/23/stub-htttpcontext/

2
Vadim

Phil Haack montre, avec l'aide de Rhino, comment se moquer de httpcontext dans asp mvc, mais j'imagine qu'il peut s'appliquer aux formulaires Web.

Clicky !!

J'espère que cela t'aides.

1
WestDiscGolf

si vous ne vous souciez pas de tester le cache, vous pouvez le faire ci-dessous:

[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"));
1
PUG

Comme tout le monde le dit ici, il y a un problème avec HTTPContext, actuellement Typemock est le seul framework qui peut le simuler directement sans aucun wrapper ni abstraction.

0
Ron_w

L'objet de cache est difficile à simuler car il s'agit d'une zone scellée du framework .NET. Je contourne généralement ceci en construisant une classe de wrapper de cache qui accepte un objet de gestionnaire de cache. Pour tester, j'utilise un gestionnaire de cache fictif; Pour la production, j'utilise un gestionnaire de cache qui accède à HttpRuntime.Cache.

Fondamentalement, je retire la cache moi-même.

0
Chris

Un exemple pour ceux qui utilisent MVC 3 et MOQ:

Ma méthode de contrôleur a la ligne suivante:

model.Initialize(HttpContext.Cache[Constants.C_CustomerTitleList] 
as Dictionary<int, string>);

En tant que tel, tout test unitaire échouera car je ne configure pas HttpContext.Cache.

Dans mon test unitaire, j'arrange comme suit:

 HttpRuntime.Cache[Constants.C_CustomerTitleList] = new Dictionary<int, string>();

 var mockRequest = new Mock<HttpRequestBase>();
 mockRequest.SetupGet(m => m.Url).Returns(new Uri("http://localhost"));

 var context = new Mock<HttpContextBase>(MockBehavior.Strict);
 context.SetupGet(x => x.Request).Returns(mockRequest.Object);
 context.SetupGet(x => x.Cache).Returns(HttpRuntime.Cache);

 var controllerContext = new Mock<ControllerContext>();
 controllerContext.SetupGet(x => x.HttpContext).Returns(context.Object);

 customerController.ControllerContext = controllerContext.Object;
0
Duncan

Pourrait essayer ...

 Isolate.WhenCalled(() => HttpContext.Current).ReturnRecursiveFake();
 var fakeSession = HttpContext.Current.Session;
 Isolate.WhenCalled(() => fakeSession.SessionID).WillReturn("1");

Toutes ces questions de programmation demandent un modèle de programmation basé sur une interface, dans lequel vous implémentez l'interface deux fois. Un pour le code réel et un pour la maquette.

L'instanciation est alors le prochain problème. Plusieurs modèles de conception peuvent être utilisés pour cela. Voir par exemple les fameux patterns GangOfFour Creational ( GOF ) ou les patterns Dependency Injection.

ASP.Net MVC utilise en fait cette approche basée sur l’interface et convient donc beaucoup mieux aux tests unitaires.

0
Rine