Verwenden von moq, um nur einige Methoden zu verspotten

75

Ich habe die folgende Methode:

public CustomObect MyMethod()
{
    var lUser = GetCurrentUser();
    if (lUser.HaveAccess)
    {
        //One behavior
    }
    else 
    {
        //Other behavior
    }

    //return CustomObject
}

Ich möchte verspotten IMyInterface.GetCurrentUser, damit MyMethodich beim Aufrufen zu einem der Codepfade gelangen kann, um ihn zu überprüfen. Wie macht man das mit Moq?

Ich mache folgendes:

var moq = new Mock<IMyInterface>();            
moq.Setup(x => x.GetCurrentUser()).Returns(lUnauthorizedUser);

//act
var lResult = moq.Object.MyMethod();

Aber aus irgendeinem Grund lResultist es immer so nullund wenn ich versuche, mich MyMethodauf das Debuggen einzulassen, springe ich immer zur nächsten Anweisung.

Jaroslaw Jakowlew
quelle
1
Wo haben Sie lUnauthorizedUserinitialisiert? Ich würde mir vorstellen, dass Sie so etwas wollen würdenmoq.Setup(x => x.GetCurrentUser()).Returns(new User() { HaveAccess = false });
Tyler Treat
1
Tyler, sicher, ich habe es nicht m setting it in the above code, just didneingefügt, um den Code kurz zu halten.
Jaroslaw Jakowlew

Antworten:

155

Dies wird als Teil-Mock bezeichnet, und wie ich es in moq kenne, muss die Klasse und nicht die Schnittstelle verspottet und dann die Eigenschaft "Callbase" für Ihr verspottetes Objekt auf "true" gesetzt werden.

Dazu müssen alle Methoden und Eigenschaften der Klasse, die Sie testen, virtuell gemacht werden. Angenommen, dies ist kein Problem, können Sie einen Test wie folgt schreiben:

 var mock = new Mock<YourTestClass>();
 mock.CallBase = true;
 mock.Setup(x => x.GetCurrentUser()).Returns(lUnauthorizedUser);
 mockedTest.Object.MyMethod();
Lee
quelle
1
Genial! Vielen Dank, war nicht t think itso einfach.
Jaroslaw Jakowlew
1
Ich habe ähnliche Antworten gesehen, aber sie vergessen zu erwähnen, um CallBaseden wahren, sehr wesentlichen Teil einzustellen, danke!
Mike de Klerk
1
Ich höre, es riecht nach Code, um Ihren Anwendungscode nur für einen Test zu ändern. Was halten Sie von diesem Szenario in diesem Licht? In meinem Fall teste ich gerade einen ASP.Net-REST-Controller, sodass ich keinen vorhersehbaren Grund sehe, den Controller in eine Unterklasse zu unterteilen, aber es scheint auch kein Problem zu sein, nur "virtuell" hinzuzufügen meine Methoden?
Evan Sevy
1
Das Nachrüsten von @ RunStar-Tests ist eine schwierige Sache. Wenn Sie den Code so ändern, dass er testbar ist, enthält er keine Tests, sodass Sie nicht vor einer Regression geschützt sind. Sachen als virtuell zu markieren ist ziemlich sicher, wenn diese Dinge gehen. Wenn Sie wirklich wollten, könnten Sie Ihre Klasse in eine Schnittstelle einbinden (was Sie sowieso in TDD tun sollten), und dann die Methodenaufrufe einrichten, die Sie verspotten möchten, um die realen Methoden aufzurufen. Meiner Meinung nach übertrieben, aber in einigen Situationen lohnt es sich, sie in der Toolbox zu haben (z. B. möchten Sie nur einen Methodenaufruf als echte Implementierung haben und der Rest wird verspottet).
Lee
36

Die Antwort von Lee erweitern ,

Sie müssen nicht alle Methoden und Eigenschaften Ihrer Klasse virtuell machen, sondern nur die, die Sie verspotten möchten.

Es sollte auch beachtet werden, dass Sie sich über die konkrete Implementierung Ihrer Klasse lustig machen sollten.

var mock = new Mock<YourTestClass>(); // vs. var mock = new Mock<IYourTestInterface>();

Wenn Ihre Klasse keinen Standardkonstruktor hat, müssen Sie auch Argumente angeben, die an sie übergeben werden sollen über:

var mock = new Mock<YourTestClass>(x, y, z);
// or
var mock = new Mock<YourTestClass>(MockBehavior.Default, x, y, z);

Wo x, y, zsind die ersten, zweiten und dritten Parameter für Ihren Konstruktor?

Und schließlich müssen Sie einschließen, wenn die Methode, die Sie verspotten möchten, geschützt ist Moq.Protected

using Moq.Protected;

TReturnType returnValue = default(TReturnType);

mock.Protected()
    .Setup<TReturnType>("YourMockMethodName", It.IsAny<int>()) // methodname followed by arguments
    .Returns(returnValue);
Charles W.
quelle
6
Es ist erwähnenswert, dass Sie nameof(YourTestClass.YourMockMethodName)anstelle von "YourMockMethodName" verwenden können. Aus diesem Grund funktioniert der Test des Methodennamens beim Refactor weiterhin.
Schütze
30

Ich hatte einen ähnlichen Fall. Ich fand, dass der folgende Code mir mehr Flexibilität gab, sowohl verspottete als auch tatsächliche Methoden aus einer bestimmten Implementierung einer Schnittstelle zu verwenden:

var mock = new Mock<ITestClass>(); // Create Mock of interface

// Create instance of ITestClass implementation you want to use
var inst = new ActualTestClass();

// Setup to call method of an actual instance
// if method returns void use mock.Setup(...).Callback(...)
mock.Setup(m => m.SomeMethod(It.IsAny<int>())
    .Returns((int x) => inst.SomeMethod(x));

Jetzt können Sie die eigentliche Methode verwenden, aber auch Dinge wie Verifysehen, wie oft sie aufgerufen wurde.

Brandon Rader
quelle
1
Dies ist eine wirklich interessante Problemumgehung. Wir können dasselbe erreichen, ohne die Klassenmethode virtuell zu machen.
Harshad Vekariya
Wie wäre es mit den Methoden, die null zurückgeben? Wird es keinen Aufruf zur Rückgabe eines Scheinobjekts geben? mock.Setup (m => m.SomeMethod (It.IsAny <int> ()) .Returns ((int x) => inst.SomeMethod (x))
Bendram
Kennen Sie eine Möglichkeit, dies für alle Methoden / Eigenschaften einer Instanz einzurichten, z. B. automatisch und nicht einzeln?
veljkoz