Pourquoi est-ce que j'obtiens une exception avec le message «Configuration non valide sur un membre non virtuel (remplaçable dans VB)…»?

176

J'ai un test unitaire où je dois me moquer d'une méthode non virtuelle qui renvoie un type booléen

public class XmlCupboardAccess
{
    public bool IsDataEntityInXmlCupboard(string dataId,
                                          out string nameInCupboard,
                                          out string refTypeInCupboard,
                                          string nameTemplate = null)
    {
        return IsDataEntityInXmlCupboard(_theDb, dataId, out nameInCupboard, out refTypeInCupboard, nameTemplate);
    }
}

J'ai donc un objet de XmlCupboardAccessclasse fictif et j'essaie de configurer une simulation pour cette méthode dans mon cas de test, comme indiqué ci-dessous

[TestMethod]
Public void Test()
{
    private string temp1;
    private string temp2;
    private Mock<XmlCupboardAccess> _xmlCupboardAccess = new Mock<XmlCupboardAccess>();
    _xmlCupboardAccess.Setup(x => x.IsDataEntityInXmlCupboard(It.IsAny<string>(), out temp1, out temp2, It.IsAny<string>())).Returns(false); 
    //exception is thrown by this line of code
}

Mais cette ligne jette une exception

Invalid setup on a non-virtual (overridable in VB) member: 
x => x.IsDataEntityInXmlCupboard(It.IsAny<String>(), .temp1, .temp2, 
It.IsAny<String>())

Une suggestion pour contourner cette exception?

Rahul Lodha
la source
De quoi dépend votre test XmlCupboardAccess?
Preston Guillot
9
c'est simple .. vous devez le marquer virtual. Moq ne peut pas se moquer d'un type concret qu'il ne peut pas remplacer.
Simon Whitehead

Réponses:

265

Moq ne peut pas se moquer des méthodes non virtuelles et des classes scellées. Lors de l'exécution d'un test à l'aide d'un objet fictif, MOQ crée en fait un type de proxy en mémoire qui hérite de votre "XmlCupboardAccess" et remplace les comportements que vous avez définis dans la méthode "SetUp". Et comme vous le savez en C #, vous ne pouvez remplacer quelque chose que s'il est marqué comme virtuel, ce qui n'est pas le cas avec Java. Java suppose que chaque méthode non statique est virtuelle par défaut.

Une autre chose que je pense que vous devriez considérer est l'introduction d'une interface pour votre "CupboardAccess" et commencez à vous moquer de l'interface à la place. Cela vous aiderait à découpler votre code et à avoir des avantages à long terme.

Enfin, il existe des frameworks tels que: TypeMock et JustMock qui fonctionnent directement avec l'IL et peuvent donc se moquer de méthodes non virtuelles. Cependant, les deux sont des produits commerciaux.

Amol
la source
59
+1 sur le fait que vous ne devez vous moquer que des interfaces. Cette question a résolu ce que je rencontrais, car je me suis accidentellement moqué de la classe et non de l'interface sous-jacente.
Paul Raff
1
Non seulement cela résout le problème, mais c'est une bonne pratique d'utiliser des interfaces pour toutes vos classes à tester. Moq vous oblige essentiellement à avoir une bonne inversion de dépendance alors que certains des autres frameworks moqueurs vous permettent de contourner ce principe.
Xipooo
Serait-ce considéré comme une violation de ce principe si j'ai une fausse implémentation, par exemple, FakePeopleRepository, de mon interface, par exemple, IPeopleRepository, et je me moque de la fausse implémentation? Je pense que l'IoC est toujours préservé car dans ma configuration de test, je dois passer le faux objet à ma classe de service qui prend l'interface dans son constructeur.
paz
1
@paz Le but de l'utilisation de MOQ est d'éviter la fausse implémentation. Considérez maintenant le nombre de variantes de la fausse implémentation dont vous auriez besoin pour vérifier les conditions aux limites, etc. En théorie, oui, vous pourriez vous moquer d'une fausse implémentation. Mais pratiquement, cela ressemble à une odeur de code.
Amol
Notez que cette erreur peut en fait se produire avec les méthodes d'extension sur les interfaces, ce qui peut prêter à confusion.
Dan Pantry
34

Pour aider quiconque a eu le même problème que moi, j'ai accidentellement mal saisi le type d'implémentation au lieu de l'interface, par exemple

var mockFileBrowser = new Mock<FileBrowser>();

au lieu de

var mockFileBrowser = new Mock<IFileBrowser>();
Ralt
la source
5

Au lieu de vous moquer de la classe concrète, vous devriez vous moquer de cette interface de classe. Extraire l'interface de la classe XmlCupboardAccess

public interface IXmlCupboardAccess
{
    bool IsDataEntityInXmlCupboard(string dataId, out string nameInCupboard, out string refTypeInCupboard, string nameTemplate = null);
}

Et au lieu de

private Mock<XmlCupboardAccess> _xmlCupboardAccess = new Mock<XmlCupboardAccess>();

changer en

private Mock<IXmlCupboardAccess> _xmlCupboardAccess = new Mock<IXmlCupboardAccess>();
Sashus
la source
3

Vous obtiendrez également cette erreur si vous vérifiez qu'une méthode d'extension d'une interface est appelée.

Par exemple si vous vous moquez:

var mockValidator = new Mock<IValidator<Foo>>();
mockValidator
  .Verify(validator => validator.ValidateAndThrow(foo, null));

Vous obtiendrez la même exception car il .ValidateAndThrow()s'agit d'une extension sur l' IValidator<T>interface.

public static void ValidateAndThrow<T>(this IValidator<T> validator, T instance, string ruleSet = null)...

Scotty.NET
la source
-12

Code:

private static void RegisterServices(IKernel kernel)
{
    Mock<IProductRepository> mock=new Mock<IProductRepository>();
    mock.Setup(x => x.Products).Returns(new List<Product>
    {
        new Product {Name = "Football", Price = 23},
        new Product {Name = "Surf board", Price = 179},
        new Product {Name = "Running shose", Price = 95}
    });

    kernel.Bind<IProductRepository>().ToConstant(mock.Object);
}        

mais voyez l'exception.

Borat
la source
4
Pourriez-vous expliquer votre solution? De plus, "voir exception ..." est laissé en suspens. Pouvez-vous développer davantage sur ce sujet?
amadan