Warum erhalte ich eine Ausnahme mit der Meldung "Ungültiges Setup für ein nicht virtuelles (in VB überschreibbares) Mitglied ..."?

176

Ich habe einen Komponententest, bei dem ich eine nicht virtuelle Methode verspotten muss, die einen Bool-Typ zurückgibt

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);
    }
}

Ich habe also ein Mock-Objekt der XmlCupboardAccessKlasse und versuche, Mock für diese Methode in meinem Testfall wie unten gezeigt einzurichten

[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
}

Aber diese Zeile löst eine Ausnahme aus

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

Irgendwelche Vorschläge, wie man diese Ausnahme umgehen kann?

Rahul Lodha
quelle
Wovon hängt Ihr Test ab XmlCupboardAccess?
Preston Guillot
9
Es ist einfach. Sie müssen es markieren virtual. Moq kann sich nicht über einen konkreten Typ lustig machen, den er nicht überschreiben kann.
Simon Whitehead

Antworten:

265

Moq kann nicht-virtuelle Methoden und versiegelte Klassen nicht verspotten. Während Sie einen Test mit einem Scheinobjekt ausführen, erstellt MOQ tatsächlich einen speicherinternen Proxy-Typ, der von Ihrem "XmlCupboardAccess" erbt und die Verhaltensweisen überschreibt, die Sie in der "SetUp" -Methode eingerichtet haben. Und wie Sie in C # wissen, können Sie etwas nur überschreiben, wenn es als virtuell markiert ist, was bei Java nicht der Fall ist. Java geht davon aus, dass jede nicht statische Methode standardmäßig virtuell ist.

Eine andere Sache, die Sie meines Erachtens in Betracht ziehen sollten, ist die Einführung einer Schnittstelle für Ihren "CupboardAccess" und die Verspottung der Schnittstelle. Dies würde Ihnen helfen, Ihren Code zu entkoppeln und auf längere Sicht Vorteile zu erzielen.

Schließlich gibt es Frameworks wie TypeMock und JustMock, die direkt mit der IL zusammenarbeiten und daher nicht virtuelle Methoden verspotten können. Beide sind jedoch kommerzielle Produkte.

Amol
quelle
59
+1 auf die Tatsache, dass Sie nur Schnittstellen verspotten sollten. Diese Frage löste, worauf ich stieß, weil ich versehentlich die Klasse und nicht die zugrunde liegende Schnittstelle verspottete.
Paul Raff
1
Dies löst nicht nur das Problem, sondern es empfiehlt sich auch, Schnittstellen für alle Klassen zu verwenden, die getestet werden müssen. Moq zwingt Sie im Wesentlichen zu einer guten Abhängigkeitsinversion, bei der Sie, wie bei einigen anderen spöttischen Frameworks, dieses Prinzip umgehen können.
Xipooo
Würde es als Verstoß gegen dieses Prinzip angesehen, wenn ich eine gefälschte Implementierung, z. B. FakePeopleRepository, meiner Schnittstelle, z. B. IPeopleRepository, habe und mich über die gefälschte Implementierung lustig mache? Ich denke, IoC bleibt erhalten, da ich in meinem Testaufbau das gefälschte Objekt an meine Serviceklasse übergeben muss, die die Schnittstelle in ihrem Konstruktor übernimmt.
Paz
1
@paz Der springende Punkt bei der Verwendung von MOQ ist, die gefälschte Implementierung zu vermeiden. Überlegen Sie nun, wie viele Varianten der gefälschten Implementierung Sie benötigen würden, um die Randbedingungen usw. zu überprüfen. Theoretisch könnten Sie eine gefälschte Implementierung verspotten. Aber praktisch klingt es wie ein Codegeruch.
Amol
Beachten Sie, dass dieser Fehler tatsächlich bei Erweiterungsmethoden auf Schnittstellen auftreten kann, was verwirrend sein kann.
Dan Pantry
34

Als Hilfe für alle, die das gleiche Problem wie ich hatten, habe ich versehentlich den Implementierungstyp anstelle der Schnittstelle falsch eingegeben, z

var mockFileBrowser = new Mock<FileBrowser>();

anstatt

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

Bitte sehen Sie, warum die Eigenschaft, die ich verspotten möchte, virtuell sein muss.

Möglicherweise müssen Sie eine Wrapper-Schnittstelle schreiben oder die Eigenschaft als virtuell / abstrakt markieren, da Moq eine Proxy-Klasse erstellt, mit der Aufrufe abgefangen und Ihre benutzerdefinierten Werte zurückgegeben werden, die Sie in den .Returns(x)Aufruf eingefügt haben.

Bryida
quelle
5

Anstatt eine konkrete Klasse zu verspotten, sollten Sie diese Klassenschnittstelle verspotten. Extrahieren Sie die Schnittstelle aus der XmlCupboardAccess-Klasse

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

Und statt

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

ändern

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

Dieser Fehler wird auch angezeigt, wenn Sie überprüfen, ob eine Erweiterungsmethode einer Schnittstelle aufgerufen wird.

Zum Beispiel, wenn Sie verspotten:

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

Sie erhalten dieselbe Ausnahme, da .ValidateAndThrow()es sich um eine Erweiterung der IValidator<T>Schnittstelle handelt.

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

Scotty.NET
quelle
-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);
}        

aber siehe Ausnahme.

Borat
quelle
4
Können Sie eine Erklärung für Ihre Lösung geben? Außerdem bleibt "Ausnahme sehen ..." hängen. Können Sie das näher erläutern?
Amadan