Méthodes d'extension moqueuses avec Moq

175

J'ai une interface préexistante ...

public interface ISomeInterface
{
    void SomeMethod();
}

et j'ai étendu cette interface en utilisant un mixin ...

public static class SomeInterfaceExtensions
{
    public static void AnotherMethod(this ISomeInterface someInterface)
    {
        // Implementation here
    }
}

J'ai une classe qui appelle ça que je veux tester ...

public class Caller
{
    private readonly ISomeInterface someInterface;

    public Caller(ISomeInterface someInterface)
    {
        this.someInterface = someInterface;
    }

    public void Main()
    {
        someInterface.AnotherMethod();
    }
}

et un test où j'aimerais me moquer de l'interface et vérifier l'appel à la méthode d'extension ...

    [Test]
    public void Main_BasicCall_CallsAnotherMethod()
    {
        // Arrange
        var someInterfaceMock = new Mock<ISomeInterface>();
        someInterfaceMock.Setup(x => x.AnotherMethod()).Verifiable();

        var caller = new Caller(someInterfaceMock.Object);

        // Act
        caller.Main();

        // Assert
        someInterfaceMock.Verify();
    }

L'exécution de ce test génère cependant une exception ...

System.ArgumentException: Invalid setup on a non-member method:
x => x.AnotherMethod()

Ma question est, y a-t-il une bonne façon de se moquer de l'appel mixin?

Russell Giddings
la source
3
D'après mon expérience, les termes mixin et méthodes d'extension sont des choses distinctes. J'utiliserais ce dernier dans ce cas pour éviter les erreurs: P
Ruben Bartelink
3
Duplicata de: stackoverflow.com/questions/562129/… .
Oliver

Réponses:

33

Vous ne pouvez pas simuler "directement" une méthode statique (d'où la méthode d'extension) avec un framework moqueur. Vous pouvez essayer Moles ( http://research.microsoft.com/en-us/projects/pex/downloads.aspx ), un outil gratuit de Microsoft qui met en œuvre une approche différente. Voici la description de l'outil:

Moles est un framework léger pour les stubs de test et les détours dans .NET basé sur des délégués.

Les taupes peuvent être utilisées pour détourner toute méthode .NET, y compris les méthodes non virtuelles / statiques dans les types scellés.

Vous pouvez utiliser Moles avec n'importe quel framework de test (c'est indépendant à ce sujet).

Daniele Armanasco
la source
2
Outre Moles, il existe d'autres frameworks moqueurs (non libres) qui utilisent l'API de profilage de .NET pour simuler des objets et peuvent donc remplacer tous les appels. Les deux que je connais sont JustMock et TypeMock Isolator de Telerik .
Marcel Gosselin
6
Les taupes en théorie, c'est bien, mais j'ai trouvé trois problèmes lorsque je l'ai testé qui m'a empêché de l'utiliser ... 1) Il ne fonctionne pas dans le coureur Resharper NUnit 2) Vous devez créer manuellement un assemblage de taupes pour chaque assemblage tronqué 3 ) Vous devez recréer manuellement un assemblage de taupe chaque fois qu'une méthode stubbed change.
Russell Giddings
26

J'ai utilisé un wrapper pour contourner ce problème. Créez un objet wrapper et transmettez votre méthode simulée.

Voir Mocking Static Methods for Unit Testing par Paul Irwin, il a de beaux exemples.

Alvis
la source
12
J'aime cette réponse car ce qu'elle dit (sans le dire directement) est que vous devez modifier votre code pour le rendre testable. C'est comme ça que ça marche. Comprenez que dans la conception de micropuce / IC / ASIC, ces puces doivent non seulement être conçues pour fonctionner, mais conçues encore plus pour être testables, car si vous ne pouvez pas tester une micropuce, elle est inutile - vous ne pouvez pas garantir qu'elle le fera. travail. Il en va de même pour les logiciels. Si vous ne l'avez pas construit pour être testable, c'est ... inutile. Construisez-le pour qu'il soit testable, ce qui dans certains cas signifie réécrire le code (et utiliser des wrappers), puis créez les tests automatisés qui le testent.
Michael Plautz
2
J'ai créé une petite bibliothèque qui englobe Dapper, Dapper.Contrib et IDbConnection. github.com/codeapologist/DataAbstractions.Dapper
Drew Sumido
15

J'ai trouvé que je devais découvrir l'intérieur de la méthode d'extension pour laquelle j'essayais de me moquer de l'entrée et de me moquer de ce qui se passait à l'intérieur de l'extension.

J'ai considéré l'utilisation d'une extension comme l'ajout de code directement à votre méthode. Cela signifiait que je devais me moquer de ce qui se passe à l'intérieur de l'extension plutôt que de l'extension elle-même.

chris31389
la source
11

Vous pouvez simuler une interface de test qui hérite de la vraie et a un membre avec la même signature que la méthode d'extension.

Vous pouvez ensuite simuler l'interface de test, ajouter la vraie à la maquette et appeler la méthode de test dans la configuration.

Votre implémentation du simulacre peut alors appeler la méthode de votre choix ou simplement vérifier que la méthode est appelée:

IReal //on which some extension method is defined
{
    ... SomeRegularMethod(...);
}

static ExtensionsForIReal
{
    static ... SomeExtensionMethod(this IReal iReal,...);
}

ITest: IReal
{
    //This is a regular method with same name and signature as the extension without the "this IReal iReal" parameter
    ... SomeExtensionMethod(...);
}

var someMock = new Mock<ITest>();
Mock.As<IReal>(); //ad IReal to the mock
someMock.Setup(x => x.SomeExtensionMethod(...)).Verifiable(); //Calls SomeExtensionMethod on ITest
someMock.As<IReal>().Setup(x => x.SomeRegularMethod(...)).Verifiable(); //Calls SomeRegularMethod on IReal

Merci à la solution Håvard S dans cet article pour savoir comment implémenter une maquette prenant en charge deux interfaces. Une fois que je l'ai trouvé, l'adapter avec l'interface de test et la méthode statique était un jeu d'enfant.

pasx
la source
Pouvez-vous nous montrer le même échantillon de signature pour SomeNotAnExtensionMethodet SomeNotAnExtensionMethod? J'ai maintenant une idée de comment créer une signature de méthode d'extension dans une interface ...
Peter Csala
1
@Peter Csala: Désolé si ce n'est pas clair. J'ai mis à jour le message pour le rendre plus clair et renommé SomeNotAnExtensionMethod en SomeRegularMethod.
pasx le
Comment passez-vous le iRealparamètre SomeExtensionMethoden cas de ITest? Si vous le transmettez comme premier paramètre, comment procédez-vous à l'installation? Setup( x=> x.SomeExtensionMethod(x, ...)cela provoquera une exception d'exécution.
Peter Csala
Vous ne le passez pas. Du point de vue de la simulation, ITest contient une méthode régulière sans le paramètre this pour faire correspondre la signature des appels à la méthode d'extension dans votre code afin que vous ne receviez jamais d'IReal ici et dans tous les cas l'implémentation d'IReal / ITest est la maquette elle-même . Si vous voulez accéder à certaines propriétés de l'IReal simulé dans SomeExtensionMethod, vous devez faire tout cela dans la simulation, par exemple: object _mockCache = whatever... `Setup (x => x.SomeExtensionMethod (...) .. Callback (() => you can accéder à _mockCache ici);)
pasx
8

Vous pouvez facilement simuler une méthode d'extension avec JustMock . L'API est la même que la méthode normale moqueuse. Considérer ce qui suit

public static string Echo(this Foo foo, string strValue) 
{ 
    return strValue; 
}

Pour organiser et vérifier cette méthode, utilisez ce qui suit:

string expected = "World";

var foo = new Foo();
Mock.Arrange(() => foo.Echo(Arg.IsAny<string>())).Returns(expected);

string result = foo.Echo("Hello");

Assert.AreEqual(expected, result);

Voici également un lien vers la documentation: Mocking des méthodes d'extension

Mihail Vladov
la source
2

J'aime utiliser le wrapper (modèle d'adaptateur) lorsque j'emballe l'objet lui-même. Je ne suis pas sûr que j'utiliserais cela pour envelopper une méthode d'extension, qui ne fait pas partie de l'objet.

J'utilise une propriété interne Lazy Injectable de type Action, Func, Predicate ou Delegate et permet d'injecter (échanger) la méthode lors d'un test unitaire.

    internal Func<IMyObject, string, object> DoWorkMethod
    {
        [ExcludeFromCodeCoverage]
        get { return _DoWorkMethod ?? (_DoWorkMethod = (obj, val) => { return obj.DoWork(val); }); }
        set { _DoWorkMethod = value; }
    } private Func<IMyObject, string, object> _DoWorkMethod;

Ensuite, vous appelez Func au lieu de la méthode réelle.

    public object SomeFunction()
    {
        var val = "doesn't matter for this example";
        return DoWorkMethod.Invoke(MyObjectProperty, val);
    }

Pour un exemple plus complet, consultez http://www.rhyous.com/2016/08/11/unit-testing-calls-to-complex-extension-methods/

Rhyous
la source
C'est bien, mais les lecteurs doivent savoir que _DoWorkMethod est un nouveau champ de la classe dont chaque instance de la classe doit maintenant allouer un champ supplémentaire. Il est rare que cela compte, mais parfois cela dépend du nombre d'instances que vous allouez à un moment donné. Vous pouvez contourner ce problème en rendant _DoWorkMethod statique. L'inconvénient est que si vous avez des tests unitaires exécutés simultanément, deux tests unitaires différents qui peuvent modifier la même valeur statique se termineront.
zumalifeguard
-1

Donc, si vous utilisez Moq et que vous souhaitez moquer le résultat d'une méthode d'extension, vous pouvez l'utiliser SetupReturnsDefault<ReturnTypeOfExtensionMethod>(new ConcreteInstanceToReturn())sur l'instance de la classe fictive qui a la méthode d'extension que vous essayez de simuler.

Ce n'est pas parfait, mais à des fins de test unitaire, cela fonctionne bien.

ckernel
la source
Je crois que c'est SetReturnsDefault <T> ()
David
cela ne renvoie jamais l'instance concrète. Juste null dans le cas d'une classe C # personnalisée!
HelloWorld le