web-dev-qa-db-fra.com

Méthodes statiques moqueuses

Récemment, j'ai commencé à utiliser Moq to unit test. J'utilise Moq pour simuler des cours que je n'ai pas besoin de tester.

Comment gérez-vous généralement les méthodes statiques?

public void foo(string filePath)
{
    File f = StaticClass.GetFile(filePath);
}

Comment cette méthode statique, StaticClass.GetFile(), pourrait-elle être moquée?

P.S. J'apprécierais tout matériel de lecture que vous recommandez sur Moq et les tests unitaires.

63
Kevin Meredith

Les frameworks moqueurs tels que Moq ou Rhinomocks ne peuvent créer que des occurrences fictives d'objets. Cela signifie que les méthodes statiques moqueuses ne sont pas possibles.

Vous pouvez également rechercher sur Google pour plus d'informations.

En outre, quelques questions ont déjà été posées sur StackOverflow ici , ici et ici .

35
Pure.Krome

@ Pure.Krome: bonne réponse mais j'ajouterai quelques détails 

@ Kevin: Vous devez choisir une solution en fonction des modifications que vous pouvez apporter au code.
Si vous pouvez le changer, une injection de dépendance rend le code plus vérifiable. Si vous ne le pouvez pas, vous avez besoin d’un bon isolement.
Avec le framework mock gratuit (Moq, RhinoMocks, NMock ...), vous ne pouvez que simuler des délégués, des interfaces et des méthodes virtuelles. Donc, pour les méthodes statiques, scellées et non virtuelles, vous avez 3 solutions: 

  • TypeMock Isolator (peut se moquer de tout mais c'est cher) 
  • JustMock de Telerik (nouveau venu, moins cher mais pas encoregratuit) 
  • Moles de Microsoft (la seule solution gratuite pour l'isolation)

Je recommande Moles, car c'est gratuit, efficace et utilise des expressions lambda comme Moq. Juste un détail important: les taupes fournissent des moignons, pas des fous. Vous pouvez donc toujours utiliser Moq pour l'interface et les délégués;) 

Mock: classe qui implémente une interface et permet de définir de manière dynamique les valeurs à renvoyer/les exceptions à exclure de méthodes particulières et permet de vérifier si des méthodes particulières ont été appelées ou non.
Stub: Comme une classe fictive, sauf qu'elle ne permet pas de vérifier que les méthodes ont été appelées/non appelées.

41
Jeco

Il existe une possibilité dans .NET à l'exclusion de MOQ et de toute autre bibliothèque moqueuse. Vous devez cliquer avec le bouton droit de la souris sur l’explorateur de solutions sur l’ensemble contenant la méthode statique que vous voulez simuler et choisir Ajouter un assemblage faux . Ensuite, vous pouvez librement vous moquer des méthodes statiques Assembly.

Supposons que vous souhaitiez simuler la méthode statique System.DateTime.Now. Faites ceci par exemple de cette façon:

using (ShimsContext.Create())
{
    System.Fakes.ShimDateTime.NowGet = () => new DateTime(1837, 1, 1);
    Assert.AreEqual(DateTime.Now.Year, 1837);
}

Vous avez une propriété similaire pour chaque propriété statique et chaque méthode.

14
pt12lol

Vous pouvez y parvenir avec Pose library disponible sur nuget. Il vous permet de simuler, entre autres, des méthodes statiques. Dans votre méthode de test, écrivez ceci:

Shim shim = Shim.Replace(() => StaticClass.GetFile(Is.A<string>()))
    .With((string name) => /*Here return your mocked value for test*/);
var sut = new Service();
PoseContext.Isolate(() =>
    result = sut.foo("filename") /*Here the foo will take your mocked implementation of GetFile*/, shim);

Pour plus de précisions, veuillez consulter ici https://medium.com/@tonerdo/unit-testing-datetime-now-in-c-without-using-interfaces-978d372478e8

4
mr100

Je me suis amusé avec un concept de refactorisation des méthodes statiques pour appeler un délégué que vous pouvez définir en externe à des fins de test.

Cela n'utiliserait aucun cadre de test et constituerait une solution entièrement personnalisée. Toutefois, le refactor n'influencera pas la signature de votre correspondant et le présentera donc relativement sûr.

Pour que cela fonctionne, vous devez avoir accès à la méthode statique. Elle ne fonctionnera donc pas pour les bibliothèques externes telles que System.DateTime.

Voici un exemple avec lequel j'ai joué, où j'ai créé deux méthodes statiques, une avec un type de retour prenant deux paramètres et un générique qui n'a pas de type de retour.

La classe statique principale:

public static class LegacyStaticClass
{
    // A static constructor sets up all the delegates so production keeps working as usual
    static LegacyStaticClass()
    {
        ResetDelegates();
    }

    public static void ResetDelegates()
    {
        // All the logic that used to be in the body of the static method goes into the delegates instead.
        ThrowMeDelegate = input => throw input;
        SumDelegate = (a, b) => a + b;
    }

    public static Action<Exception> ThrowMeDelegate;
    public static Func<int, int, int> SumDelegate;

    public static void ThrowMe<TException>() where TException : Exception, new()
        => ThrowMeDelegate(new TException());

    public static int Sum(int a, int b)
        => SumDelegate(a, b);
}

Les tests unitaires (xUnit et Shouldly)

public class Class1Tests : IDisposable
{
    [Fact]
    public void ThrowMe_NoMocking_Throws()
    {
        Should.Throw<Exception>(() => LegacyStaticClass.ThrowMe<Exception>());
    }

    [Fact]
    public void ThrowMe_EmptyMocking_DoesNotThrow()
    {
        LegacyStaticClass.ThrowMeDelegate = input => { };

        LegacyStaticClass.ThrowMe<Exception>();

        true.ShouldBeTrue();
    }

    [Fact]
    public void Sum_NoMocking_AddsValues()
    {
        LegacyStaticClass.Sum(5, 6).ShouldBe(11);
    }

    [Fact]
    public void Sum_MockingReturnValue_ReturnsMockedValue()
    {
        LegacyStaticClass.SumDelegate = (a, b) => 6;
        LegacyStaticClass.Sum(5, 6).ShouldBe(6);
    }

    public void Dispose()
    {
        LegacyStaticClass.ResetDelegates();
    }
}
1
Joe_DM

J'ai bien aimé Pose, mais je ne pouvais pas le faire cesser de lancer InvalidProgramException, qui semble être un problème connu numéro . Maintenant, j'utilise Smocks comme ceci:

Smock.Run(context =>
{
    context.Setup(() => DateTime.Now).Returns(new DateTime(2000, 1, 1));

    // Outputs "2000"
    Console.WriteLine(DateTime.Now.Year);
});
1
sirdank

Je sais que c'est un peu tard mais cette solution détournée m'a permis de me moquer d'une méthode statique utilisant Moq.

Pour ce faire, j'ai créé une classe (appelons-la Placeholder) dont l'une des méthodes s'appelle la méthode statique StaticClass.GetFile.

public class Placeholder{  

    //some empty constructor

    public File GetFile(){

        File f = StaticClass.GetFile(filePath);
        return f;
    }
}

Ensuite, au lieu d'appeler StaticClass.GetFile dans foo, j'ai créé une instance de Placeholder et appelé la fonction GetFile

public void foo(string filePath)
{
    Placeholder p = new Placeholder();
    File f = p.GetFile(filePath);
}

Maintenant, lors des tests unitaires, au lieu d'essayer de simuler StaticClass.GetFile, je pouvais me moquer de la méthode non statique GetFile de la classe Placeholder

0
Justin Borromeo