33 votes

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 qui utilise la fonction HttpContext.Current.Cache classe J'obtiens une erreur en utilisant NUnit. La fonctionnalité est basique - vérifier si un élément est dans le cache, et si non, le créer et le mettre dedans :

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

Lorsque l'on appelle cette fonction à partir d'un test unitaire, elle échoue avec l'erreur suivante NullReferenceException lors de la rencontre avec le premier Cache ligne. En Java, j'utiliserais Cactus pour tester le code côté serveur. Existe-t-il un outil similaire que je peux utiliser pour le code C# ? Cette question de l'OS mentionne les frameworks mock - est-ce le seul moyen 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 le Cache est nulle car je ne veux pas écrire de code spécifiquement pour le test unitaire et supposer qu'il sera toujours valide lorsqu'il sera exécuté sur un serveur. Est-ce valable, ou dois-je ajouter des contrôles de nullité autour du cache ?

42voto

Orion Edwards Points 54939

Le moyen d'y parvenir est d'éviter l'utilisation directe de HttpContext ou d'autres classes similaires, et de les remplacer par des mocks. Après tout, vous n'essayez pas de tester que le HttpContext fonctionne correctement (c'est le travail de Microsoft), vous essayez juste de tester que les méthodes ont été appelées quand elles auraient dû.

Étapes (au cas où vous voudriez juste connaître la technique sans avoir à fouiller dans des tas de blogs) :

  1. Créez une interface qui décrit les méthodes que vous voulez utiliser dans votre mise en cache (probablement des choses comme GetItem, SetItem, ExpireItem). Appelez-la ICache ou ce que vous voulez

  2. Créez une classe qui implémente cette interface et transmet les méthodes au véritable HttpContext.

  3. Créez une classe qui implémente la même interface et qui agit comme un cache fictif. Elle peut utiliser un dictionnaire ou autre chose si vous souhaitez sauvegarder des objets.

  4. Modifiez votre code original pour qu'il n'utilise pas du tout le HttpContext, mais uniquement un ICache. Le code devra alors obtenir une instance de l'ICache - vous pouvez soit passer une instance dans le constructeur de vos classes (c'est tout ce que l'injection de dépendance est vraiment), soit la placer dans une variable globale.

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

  6. Profit !

29voto

Brian Surowiec Points 4141

Je suis d'accord avec les autres pour dire que l'utilisation d'une interface serait la meilleure option, mais parfois il n'est pas possible de modifier un système existant. Voici un code que j'ai assemblé à partir d'un de mes projets et qui devrait vous donner les résultats que vous recherchez. Il est loin d'être joli ou d'être une bonne solution, mais si vous ne pouvez vraiment pas modifier votre code, il devrait faire l'affaire.

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

17voto

Shawn Miller Points 3875
HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));

6voto

Steve Wright Points 1085

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

Justin Etheredge a un grand poste sur la façon de simuler HttpContext (qui contient la classe de cache).

D'après l'exemple de Justin, je passe un HttpContextBase à mes contrôleurs en utilisant le HttpContextFactory.GetHttpContext. Lorsque je les simule, je construis simplement un Mock pour faire des appels à l'objet de cache.

5voto

ClearCloud8 Points 1237

Il existe une approche plus récente qui permet de traiter spécifiquement le cache dans les tests unitaires.

Je recommande d'utiliser la nouvelle version de Microsoft MemoryCache.Default approche. Vous devez utiliser .NET Framework 4.0 ou une 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 décidez de tester unitairement ces mêmes méthodes, l'objet 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 pas nécessairement besoin de simuler le composant de cache.

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