web-dev-qa-db-fra.com

Implémentation d'objets moqueurs avec Moq lorsque le constructeur a des paramètres

J'ai lu ceci réponse par Ragzitsu pour la même question. Je ne sais toujours pas comment mettre en œuvre les choses. Quelqu'un peut-il me donner un exemple de mise en œuvre.

J'ai les cours suivants:

class Fizz : IFizz
{
}

class Buzz : IBuzz
{

}

class Bar : IBar
{

}

class Foo : IFoo
{
    public Foo(IBar bar, IFizz fizz, IBuzz buzz)
    {
        //initialize etc.
    }

    //public methods
}

Quelle est la manière pratique de contourner le constructeur ici? Je veux faire quelque chose comme

var foo = new Mock<IFoo>();

En d'autres termes, comment le code suivrait-il les conseils

The best thing to do would be right click on your class and choose Extract interface.

22
happygilmore

Vous pouvez créer une maquette où le constructeur a des arguments param, en faisant référence à MockBehavior, illustré ci-dessous

Mock<testClass>(MockBehavior.Strict, new object[] {"Hello"}); 
26
Ashraf Alam

TL; DR

On ne sait pas vraiment ce que vous essayez de faire.

Si vous avez déjà défini l'interface IFoo (ce que votre question semble montrer) et que vous devez vous en moquer à un autre type, vous êtes déjà défini: var bazz = new Bazz(new Mock<IFoo>());

Si vous avez une classe concrète Foo que vous n'avez pas abstraite dans une interface que vous devez fournir à un autre type en tant que dépendance, l'extraction d'une interface est l'un des moyens les plus courants pour permettre l'initialisation d'un Bazz objet sans créer une classe Foo réelle. En fonction de l'apparence réelle du code, vous pouvez limiter la portée en faisant abstraction dans IFoo uniquement les parties de Foo dont Bazz dépend. L'autre façon est de se moquer du béton et de fournir à moq les arguments constructeur à utiliser. Vous devrez vous assurer que les parties de dépendance de Foo sont "Mock-able" (public virtuel) afin de fournir au Mock votre configuration et vos restuls.

Modifier:

En tant que montage pour répondre réellement à la question de

comment le code suivrait-il les conseils La meilleure chose à faire serait de ... Extraire l'interface?

Le conseil pour extraire une interface peut ne pas être exactement ce que vous recherchez. Comme cela a été mentionné dans d'autres réponses, nous ne savons pas ce que vous essayez de tester .

Si vous essayez de tester la fonctionnalité sur le béton Foo implémentation de IFoo puis en extrayant une interface et en se moquant IFoo ne va pas vraiment vous aider, car moq va implémenter l'interface elle-même et ne fournir que la "fonctionnalité" que vous lui dites à fournir dans les méthodes d'installation et les propriétés de cette implémentation. Si c'est ce que vous faites, [~ # ~] et [~ # ~] la méthode testée appelle une autre méthode sur le béton Foo implémentation, [~ # ~] et [~ # ~] cette méthode est quelque chose pour laquelle vous voulez fournir une abstraction, alors je voudrais faites comme @Chris Marisic et changez la méthode que vous souhaitez abstraire en virtual. Avec ceci comme virtuel, vous pouvez utiliser un cadre de simulation pour fournir l'abstraction, ou faire comme je l'ai fait dans le passé et créer un sous-type à partir du sujet sous test dans la classe de test.

public class MyFooTests
{
    ... // Tests
    private class SubFoo : Foo
    {
        public override void MethodToAbstract(bool par)
        {
            // do something expected
        }
    }
}

Avec cette méthode, vous devrez toujours fournir quelque chose au constructeur de Foo, mais ce serait un cas idéal pour utiliser des simulations sur ces interfaces.

Maintenant, si vous testez Foo et que la méthode testée appelle simplement des méthodes sur IBar, IFizz, IBuzz, et qu'il n'y a pas d'autre abstraction que vous voulez créer, alors vous êtes déjà configuré. Modifiez ces interfaces, configurez-les pour renvoyer ce que vous attendez dans un cas de test particulier et fournissez Foo avec les objets factices.

Les conseils d'interface d'extrait sont vraiment utiles lorsque votre sujet sous test dépend de Foo, comme mentionné par @Chris Marisic. Cependant, je ne partage peut-être pas son sentiment de ne pas vouloir créer d'interfaces dans cette situation. Cela dépend de ce qui vous sent le plus: créer une interface pour fournir une simulation, ou changer le modificateur pour fournir la même chose. Si vous voulez changer le modificateur et toujours utiliser moq pour se moquer du béton Foo, vous devrez soit exposer un constructeur par défaut ( assez malodorant pour moi ) ou créez la maquette avec la spécification d'arguments constructeur donnée, comme mentionné dans votre réponse référencée .

Disons que vous disposez des éléments suivants:

public class Baz
{
    private Foo foo;

    public Baz(Foo inputFoo)
    {
        this.foo = inputFoo;
    }

    public bool GetFromTheFoo()
    {
        // other stuff here
        var fooStuff = this.foo.GetStuff();
        // logic on the stuff;
        return fooStuff != null;
    }
}

Dans ce qui précède, Baz dépend de Foo. Après avoir extrait une interface de Foo, Baz devrait accepter IFoo.

Foo ressemblerait alors à votre question et IFoo aurait une définition de signature pour la méthode que vous voulez abstraire de Baz.

object GetStuff();

Maintenant, dans votre test, vous utiliserez toujours var foo = new Mock<IFoo>(); et donnerez foo à votre Baz sujet de test. N'oubliez pas de configurer la méthode IFoo.GetStuff().

foo.Setup(f => f.GetStuff()).Returns(new object());

Comme vous avez maintenant créé l'abstraction IFoo, il n'est pas nécessaire de

contourner le constructeur ici?

parce que la maquette de IFoo n'a qu'un constructeur par défaut fourni par moq.

Personnellement, je préfère l'approche d'interface lorsqu'elle s'applique, car elle supprime IFizz, IBuzz et IBar de la chaîne de dépendance sur Baz. Si Baz dépend de Foo et Foo dépend de IFizz etc., Alors Baz dépend également de IFizz etc. Après extraction de IFoo, Baz dépend uniquement de IFoo.


Réponse originale (pas une réponse réelle # chagrin )

Je sais que cela a été demandé il y a un certain temps et peut-être n'a-t-il aucune valeur pour moi d'ajouter ceci maintenant, mais pour les futurs lecteurs, ne nous attardons pas à penser que les éléments suivants sont la même chose lors de l'utilisation de moq:

var fooDouble = new Mock<IFoo>();

n'est pas la même chose que:

var fooDouble = new Mock<Foo>();

Ma compréhension est que lors de la création d'une maquette d'IFoo, le framework moq va générer une double classe qui implémente l'interface IFoo, fournissant ainsi le constructeur par défaut pour lui-même dans l'implémentation générée. La maquette de Foo proprement dite, cependant, n'implémente pas l'interface directement dans le code généré, mais hérite plutôt de la classe Foo concrète et nécessite donc que toutes les méthodes à stubper soient rendues virtuelles, et s'il existe des implémentations concrètes que vous souhaitez pour utiliser réellement (l'unité en cours de test, par exemple), vous aurez alors besoin de "CallBase = true" sur votre maquette. Si c'est ce que vous cherchez à faire avec une classe concrète qui a des arguments de constructeur, vous cherchez peut-être la réponse de @ Palkin pour éviter d'exposer un constructeur par défaut.

10
Josh Gust

Vous ne devriez rien changer si vous avez une interface IFoo et que vous voulez vous moquer de la classe Foo qui a un constructeur avec des paramètres.

Votre code est exactement ce dont vous avez besoin.

var foo = new Mock<IFoo>();

Les conseils suivants couvrent les situations où la classe n'a ni interface ni constructeur sans paramètre. En fait, vous pouvez passer tous les paramètres nécessaires au constructeur de Mock.

var mock = new Mock<IFoo>("constructor", "arguments");

mais

"La meilleure chose à faire serait de faire un clic droit sur votre classe et de choisir Extraire l'interface." (c)

8
Ilya Palkin

La meilleure chose à faire serait de faire un clic droit sur votre classe et de choisir Extraire l'interface.

Je vais aborder ce concept dans un sens orthogonal. Je ne suis pas d'accord avec cette affirmation, ne interface n'est pas la solution à la situation en question.

Pour revenir au texte de la question précédente:

public class CustomerSyncEngine {
    public CustomerSyncEngine(ILoggingProvider loggingProvider, 
                              ICrmProvider crmProvider, 
                              ICacheProvider cacheProvider) { ... }

    public void MethodWithDependencies() {
        loggingProvider.Log();
        crmProvider.Crm();
        cacheProvider.Cache();
    }
}

Notez la méthode que j'ai ajoutée.

Je crois que la vraie question est quand vous ne testez pas spécifiquement CustomerSyncEngine mais testez plutôt une classe qui dépend sur CustomerSyncEngine. Appelons cette classe SuperSyncEngine. La création d'un test contre SuperSyncEngine va être pénible car vous devez simuler l'intégralité de CustomerSyncEngine avec ses 3 interfaces, ainsi que toutes les autres dépendances supplémentaires SuperSyncEngine.

Étant donné que le code que vous cherchez à tester est SuperSyncEngine qui dépend de CustomerSyncEngine une interface n'est pas la réponse ici. Vous pourriez créer ICustomerSyncEngine mais cette interface ne devrait pas être créée simplement pour un framework de simulation. La meilleure solution consiste à modifier CustomerSyncEngine.MethodWithDependencies être virtuel

public virtual void MethodWithDependencies() {
    loggingProvider.Log();
    crmProvider.Crm();
    cacheProvider.Cache();
}

Cela vous permettrait de remplacer la méthode par un framework de moquerie ignorant les dépendances fournies par CustomerSyncEngine.

Si vous suivez cette approche, vous aurez probablement besoin d'un constructeur par défaut exposé sur CustomerSyncEngine pour lui permettre de se moquer. Il est possible que vous puissiez contourner cela et satisfaire les dépendances avec une valeur nulle ou d'autres valeurs, mais ce serait un travail supplémentaire lorsque l'objectif est de réduire la friction.

7
Chris Marisic

C'est une sorte de vieux post mais je viens de rencontrer un problème similaire (en commençant par Moq). Au cas où quelqu'un d'autre aurait un problème similaire, voici ce que j'avais:

class Bar : IBar
{
}

class Foo : IFoo
{
    public Foo(IBar bar)
    {
        //initialize etc.
    }

    //public methods
}

class Manager : IManager
{
    public Manager(Foo foo)
    {
        //initialize etc
    }
}

Ce que j'essaie de faire, c'est de tester Manager pas Foo.

Voici mon code de test initial qui a généré une erreur.

[TestFixture]
public class ManagerTest
{
    [Test]
    public void SomeTest()
    {
        var fooMock = Mock<IFoo>();
        var managerUnderTest = new Manager(fooMock.Object);
    }
}

L'erreur est Castle.DynamicProxy.InvalidProxyConstructorArgumentsException : Can not instantiate proxy of class: Something.Models.Foo. Could not find a parameterless constructor.

En lisant le message d'erreur, Moq ne comprend pas comment instancier Foo, car il n'y a pas de constructeur sans paramètre, et nous ne disons pas à Moq comment en instancier un avec des paramètres. Remplacez cette deuxième section par:

[TestFixture]
public class ManagerTest
{
    [Test]
    public void SomeTest()
    {
        var barMock = Mock<IBar>();
        var fooMock = Mock<IFoo>(barMock.Object);
        var managerUnderTest = new Manager(fooMock.Object);

        //proceed with test
    }
}
5
therealjumbo

Si vous allez tester votre classe FOo, vous n'avez pas besoin de vous moquer. Vous avez seulement besoin de vous moquer des classes qui dépendent de la classe que vous essayez de tester.

Quelque chose comme:

Mock<IBar> bar = new Mock<IBar>();
Mock<IBuzz> buzz = new Mock<IBuzz>();
Mock<IFizz> fizz= new Mock<IFizz>();

Foo foo = new Foo(bar.Object, buzz.Object, fizz.Object);

Appelez ensuite la méthode dans foo que vous souhaitez tester;)

Si la méthode dans foo utilise une méthode à l'intérieur de bar/fuzz ou fizz, alors vous devriez utiliser la sintax comme:

buzz.Setup(x => x.DoSomething()).Returns(1);

De cette façon, lorsque votre méthode foo est appelée, elle appelle le DoSomething et renvoie toujours 1;)

3
Oscar Bralo

Les interfaces sont utiles lorsqu'un type a plusieurs implémentations. D'un point de vue négatif, cela signifie que vous devez faire attention à ne pas dériver des interfaces juste parce que vous voulez des interfaces (erreur courante). Du côté positif, l'interface simulée est la deuxième implémentation par définition.

Donc, la conclusion est - si un type agit comme une dépendance d'un autre type, alors c'est un bon candidat pour implémenter une interface. Dans ce cas, vous pourrez simuler librement et complètement l'interface dans les tests unitaires.

Sur une note connexe, lors de la définition d'une interface, assurez-vous d'ajouter uniquement des parties fonctionnelles à l'interface: méthodes qui définissent ce que l'objet fait, pas à quoi il ressemble. Avoir une interface avec un tas de getters/setters n'ajoute pas de valeur à la conception. Il s'agit d'un domaine théorique assez vaste, et cette minuscule fenêtre n'est pas le lieu d'écrire plus à ce sujet.

Pour clarifier le lien avec votre question: l'implémentation simulée devrait fournir le comportement requis par l'interface. Pour ce faire, vous utilisez les fonctionnalités du framework de simulation. Cela n'a rien à voir avec l'implémentation concrète de la classe Foo - vous définissez le comportement spécifique de l'objet Mock.

0
Zoran Horvat