web-dev-qa-db-fra.com

Comment se moquer des méthodes statiques en c # en utilisant le framework MOQ?

Je faisais des tests unitaires récemment et je me suis bien moqué de plusieurs scénarios en utilisant la structure MOQ et MS Test. Je sais que nous ne pouvons pas tester les méthodes privées, mais je veux savoir si nous pouvons nous moquer des méthodes statiques en utilisant MOQ.

48
Himanshu

Moq (et autres frameworks moqueurs basés sur DynamicProxy ) sont incapables de se moquer de tout ce qui n'est pas une méthode virtuelle ou abstraite.

Les classes/méthodes scellées/statiques ne peuvent être falsifiées qu'avec des outils basés sur l'API Profiler, tels que Typemock (commercial) ou Microsoft Moles (gratuit, appelé Fakes dans Visual Studio 2012 Ultimate/2013/2015).

Vous pouvez également refactoriser votre conception pour abstraire des appels à des méthodes statiques et fournir cette abstraction à votre classe via une injection de dépendance. Dans ce cas, non seulement vous aurez un meilleur design, mais vous pourrez également le tester avec des outils gratuits, comme Moq.

Un modèle commun permettant la testabilité peut être appliqué sans aucun outil. Considérez la méthode suivante:

public class MyClass
{
    public string[] GetMyData(string fileName)
    {
        string[] data = FileUtil.ReadDataFromFile(fileName);
        return data;
    }
}

Au lieu d'essayer de se moquer de FileUtil.ReadDataFromFile, vous pouvez l'envelopper dans un protected virtual méthode, comme ceci:

public class MyClass
{
    public string[] GetMyData(string fileName)
    {
        string[] data = GetDataFromFile(fileName);
        return data;
    }

    protected virtual string[] GetDataFromFile(string fileName)
    {
        return FileUtil.ReadDataFromFile(fileName);
    }
}

Ensuite, dans votre test unitaire, dérivez de MyClass et appelez-le TestableMyClass. Ensuite, vous pouvez remplacer la méthode GetDataFromFile pour renvoyer vos propres données de test.

J'espère que ça t'as aidé.

89
Igal Tabachnik

Une autre option pour transformer la méthode statique en Func ou Action statique. Par exemple.

Code d'origine:

    class Math
    {
        public static int Add(int x, int y)
        {
            return x + y;
        }

Vous voulez "simuler" la méthode Add, mais vous ne pouvez pas. Changez le code ci-dessus en ceci:

        public static Func<int, int, int> Add = (x, y) =>
        {
            return x + y;
        };

Le code client existant n'a pas à changer (peut-être à recompiler), mais la source reste la même.

Maintenant, à partir du test unitaire, pour changer le comportement de la méthode, il suffit de lui réaffecter une fonction en ligne:

    [TestMethod]
    public static void MyTest()
    {
        Math.Add = (x, y) =>
        {
            return 11;
        };

Mettez la logique de votre choix dans la méthode ou renvoyez simplement une valeur codée en dur, en fonction de ce que vous essayez de faire.

Ce n'est peut-être pas forcément quelque chose que vous pouvez faire à chaque fois, mais en pratique, j'ai trouvé cette technique très efficace.

[edit] Je vous suggère d'ajouter le code de nettoyage suivant à votre classe de tests unitaires:

    [TestCleanup]
    public void Cleanup()
    {
        typeof(Math).TypeInitializer.Invoke(null, null);
    }

Ajoutez une ligne distincte pour chaque classe statique. Cela a pour effet que, une fois que le test unitaire est terminé, tous les champs statiques sont réinitialisés à leur valeur d'origine. De cette manière, les autres tests unitaires du même projet commenceront avec les valeurs par défaut correctes, par opposition à la version simulée.

42
zumalifeguard

Comme mentionné dans les autres réponses, le MOQ ne peut pas se moquer des méthodes statiques et, en règle générale, il convient d'éviter la statique autant que possible.

Parfois ce n'est pas possible. On travaille avec du code hérité ou tiers ou même avec les méthodes BCL qui sont statiques.

Une solution possible consiste à envelopper la statique dans un proxy avec une interface qui peut être simulée

    public interface IFileProxy {
        void Delete(string path);
    }

    public class FileProxy : IFileProxy {
        public void Delete(string path) {
            System.IO.File.Delete(path);
        }
    }

    public class MyClass {

        private IFileProxy _fileProxy;

        public MyClass(IFileProxy fileProxy) {
            _fileProxy = fileProxy;
        }

        public void DoSomethingAndDeleteFile(string path) {
            // Do Something with file
            // ...
            // Delete
            System.IO.File.Delete(path);
        }

        public void DoSomethingAndDeleteFileUsingProxy(string path) {
            // Do Something with file
            // ...
            // Delete
            _fileProxy.Delete(path);

        }
    }

L'inconvénient est que l'acteur peut devenir très encombré s'il y a beaucoup de procurations (bien que l'on puisse faire valoir que s'il y a beaucoup de procurations, la classe peut essayer de faire trop et peut être refactorée)

Une autre possibilité est d'avoir un 'proxy statique' avec différentes implémentations de l'interface derrière celle-ci

   public static class FileServices {

        static FileServices() {
            Reset();
        }

        internal static IFileProxy FileProxy { private get; set; }

        public static void Reset(){
           FileProxy = new FileProxy();
        }

        public static void Delete(string path) {
            FileProxy.Delete(path);
        }

    }

Notre méthode devient maintenant

    public void DoSomethingAndDeleteFileUsingStaticProxy(string path) {
            // Do Something with file
            // ...
            // Delete
            FileServices.Delete(path);

    }

Pour les tests, nous pouvons définir la propriété FileProxy sur notre maquette. L'utilisation de ce style réduit le nombre d'interfaces à injecter mais rend les dépendances un peu moins évidentes (mais pas plus que les appels statiques d'origine je suppose).

7
AlanT

Moq ne peut pas se moquer d'un membre statique d'une classe.

Lors de la conception de code pour la testabilité, il est important d'éviter les membres statiques (et les singletons). Un modèle de conception qui peut vous aider à refactoriser votre code à des fins de testabilité est l'injection de dépendance.

Cela signifie changer ceci:

public class Foo
{
    public Foo()
    {
        Bar = new Bar();
    }
}

à

public Foo(IBar bar)
{
    Bar = bar;
}

Cela vous permet d'utiliser une maquette de vos tests unitaires. En production, vous utilisez un outil d’injection de dépendances tel que Ninject ou nity qui peut tout relier entre eux.

J'ai écrit un blog à ce sujet il y a quelque temps. Il explique quels modèles peuvent être utilisés pour un meilleur code testable. Peut-être que cela peut vous aider: Tests unitaires, enfer ou paradis?

Une autre solution pourrait être d'utiliser le Microsoft Fakes Framework . Ce n'est pas un remplacement pour écrire un code testable conçu, mais cela peut vous aider. Le framework Fakes vous permet de vous moquer de membres statiques et de les remplacer au moment de l'exécution par votre propre comportement personnalisé.

6
Wouter de Kort