web-dev-qa-db-fra.com

Lorsque vous vous moquez d'une classe avec Moq, comment puis-je appeler la base de données pour des méthodes spécifiques?

J'apprécie vraiment le comportement moqueur Loose de Moq qui renvoie les valeurs par défaut lorsqu'aucune attente n'est définie. C'est pratique et me fait économiser du code, et cela agit également comme une mesure de sécurité: les dépendances ne seront pas appelées involontairement pendant le test unitaire (tant qu'elles sont virtuelles).

Cependant, je ne sais pas comment conserver ces avantages lorsque la méthode testée se trouve être virtuelle.
Dans ce cas, je veux appeler le code réel pour cette méthode, tout en ayant le reste de la classe vaguement moquée.

Tout ce que j'ai trouvé dans ma recherche, c'est que je pourrais définir mock.CallBase = true Pour m'assurer que la méthode est appelée. Cependant, cela affecte toute la classe. Je ne veux pas faire cela parce que cela me met dans un dilemme concernant toutes les autres propriétés et méthodes de la classe qui cachent les dépendances d'appel: si CallBase est vrai, je dois soit

  1. Stubs d'installation pour toutes les propriétés et méthodes qui masquent les dépendances - Même si mon test ne pense pas qu'il doit se soucier de ces dépendances, ou
  2. J'espère que je n'oublie pas de configurer des stubs (et qu'aucune nouvelle dépendance ne sera ajoutée au code à l'avenir) - Les tests unitaires de risque atteignent une vraie dépendance.

Ce que je pense que je veux, c'est quelque chose comme:
mock.Setup(m => m.VirtualMethod()).CallBase();
de sorte que lorsque j'appelle mock.Object.VirtualMethod(), Moq appelle l'implémentation réelle ...

Q: Avec Moq, y a-t-il un moyen de tester une méthode virtuelle, quand je me suis moqué de la classe pour ne stub que quelques dépendances? Ie Sans recourir à CallBase = true et avoir à stuber toutes les dépendances?


Exemple de code pour illustrer
(utilise MSTest, InternalsVisibleTo DynamicProxyGenAssembly2)

Dans l'exemple suivant, TestNonVirtualMethod réussit, mais TestVirtualMethod échoue - renvoie null.

public class Foo
{
    public string NonVirtualMethod() { return GetDependencyA(); }
    public virtual string VirtualMethod() { return GetDependencyA();}

    internal virtual string GetDependencyA() { return "! Hit REAL Dependency A !"; }
    // [... Possibly many other dependencies ...]
    internal virtual string GetDependencyN() { return "! Hit REAL Dependency N !"; }
}

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestNonVirtualMethod()
    {
        var mockFoo = new Mock<Foo>();
        mockFoo.Setup(m => m.GetDependencyA()).Returns(expectedResultString);

        string result = mockFoo.Object.NonVirtualMethod();

        Assert.AreEqual(expectedResultString, result);
    }

    [TestMethod]
    public void TestVirtualMethod() // Fails
    {
        var mockFoo = new Mock<Foo>();
        mockFoo.Setup(m => m.GetDependencyA()).Returns(expectedResultString);
        // (I don't want to setup GetDependencyB ... GetDependencyN here)

        string result = mockFoo.Object.VirtualMethod();

        Assert.AreEqual(expectedResultString, result);
    }

    string expectedResultString = "Hit mock dependency A - OK";
}
42
Daryn

Je crois que la réponse de Lunivore était correcte au moment où elle a été écrite.

Dans les nouvelles versions de Moq (je pense que depuis la version 4.1 de 2013), il est possible de faire ce que vous voulez avec la syntaxe exacte que vous proposez. C'est:

mock.Setup(m => m.VirtualMethod()).CallBase();

Cela configure la maquette lâche pour appeler l'implémentation de base de VirtualMethod au lieu de renvoyer simplement default(WhatEver), mais pour ce membre (VirtualMethod) uniquement.


Comme le note l'utilisateur BornToCode dans les commentaires, cela ne fonctionnera pas si la méthode a return type void. Lorsque le VirtualMethod n'est pas nul, l'appel Setup donne un Moq.Language.Flow.ISetup<TMock, TResult> Qui hérite de la méthode CallBase() de Moq.Language.Flow.IReturns<TMock, TResult>. Mais lorsque la méthode est vide, nous obtenons un Moq.Language.Flow.ISetup<TMock> À la place qui n'a pas la méthode CallBase() désirée.

Mise à jour: andrew.rockwell note ci-dessous que cela fonctionne maintenant pour les méthodes void, et apparemment cela a été corrigé dans la version 4.10 (à partir de 2018).

16
Jeppe Stig Nielsen

Étant donné que personne n'a répondu à cette question depuis des lustres et que je pense qu'elle mérite une réponse, je vais me concentrer sur la question de plus haut niveau que vous avez posée: comment conserver ces avantages lorsque la méthode testée se trouve être virtuelle.

Réponse rapide: vous ne pouvez pas le faire avec Moq, ou du moins pas directement. Mais vous pouvez le faire.

Disons que vous avez deux aspects du comportement, où l'aspect A est virtuel et l'aspect B ne l'est pas. Cela reflète à peu près ce que vous avez dans votre classe. B peut utiliser d'autres méthodes ou non; c'est à vous.

Pour le moment, votre classe Foo fait deux choses - à la fois A et B. Je peux dire que ce sont des responsabilités séparées simplement parce que vous voulez simuler A et tester B seul.

Au lieu d'essayer de simuler la méthode virtuelle sans vous moquer de quoi que ce soit d'autre, vous pouvez:

  • déplacer le comportement A dans une classe distincte
  • dépendance injecte la nouvelle classe avec A dans le constructeur de Foo via le constructeur de Foo
  • invoquez cette classe depuis B.

Vous pouvez maintenant vous moquer de A, et toujours appeler le vrai code pour B..N sans réellement appeler le vrai A. Vous pouvez soit garder A virtuel, soit y accéder via une interface et vous en moquer. Cela est également conforme au principe de responsabilité unique.

Vous pouvez mettre en cascade les constructeurs avec Foo - faire en sorte que le constructeur Foo() appelle le constructeur Foo(new A()) - donc vous n'avez même pas besoin d'un framework d'injection de dépendances pour ce faire.

J'espère que cela t'aides!

8
Lunivore