webentwicklung-frage-antwort-db.com.de

Mocking-Objekte mit Moq implementieren, wenn der Konstruktor Parameter hat

Ich habe diese Antwort von Ragzitsu für die gleiche Frage gelesen. Ich bin immer noch verwirrt, wie man Dinge umsetzt. Kann mir jemand ein Beispiel für eine Implementierung geben.

Ich habe folgende Kurse:

class Fizz : IFizz
{
}

class Buzz : IBuzz
{

}

class Bar : IBar
{

}

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

    //public methods
}

Wie kann man den Konstrukteur hier praktisch umgehen? Ich möchte so etwas tun

var foo = new Mock<IFoo>();

Mit anderen Worten, wie würde der Code nach dem Rat aussehen?

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

22
happygilmore

Sie können einen Mock erstellen, bei dem der Konstruktor über Parameterargumente verfügt, indem Sie auf MockBehavior verweisen (siehe unten)

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

TL; DR

Es ist unklar, was Sie wirklich versuchen.

Wenn Sie das Interface bereits definiert haben IFoo (was Ihre Frage zu zeigen scheint) und Sie es mit einem anderen Typ verspotten müssen, sind Sie bereits festgelegt: var bazz = new Bazz(new Mock<IFoo>());

Wenn Sie eine konkrete Klasse Foo haben, die Sie nicht in eine Schnittstelle abstrahiert haben, die Sie als Abhängigkeit für einen anderen Typ bereitstellen müssen, ist das Extrahieren einer Schnittstelle eine der gebräuchlichsten Methoden, um die Initialisierung von zu ermöglichen ein Bazz -Objekt, ohne eine tatsächliche Foo -Klasse zu erstellen. Abhängig davon, wie Ihr tatsächlicher Code aussieht, können Sie den Umfang einschränken, indem Sie nur die Teile von IFoo in Foo abstrahieren, von denen Bazz abhängt. Die andere Möglichkeit besteht darin, den Beton zu verspotten und moq mit den zu verwendenden Konstruktor-Argumenten zu versehen. Sie müssen sicherstellen, dass die Abhängigkeitsteile von Foo "Mock-fähig" (öffentlich virtuell) sind, um den Mock mit Ihrem Setup und Restuls zu versorgen.

Bearbeiten:

Als redigieren Sie, um die Frage von wirklich zu beantworten

wie würde der Code nach dem Rat aussehen? Das Beste, was zu tun ist, ist ... Schnittstelle extrahieren?

Der Rat, eine Schnittstelle zu extrahieren, ist möglicherweise nicht genau das, wonach Sie suchen. Wie bereits in anderen Antworten erwähnt, sind wir nicht sicher, was Sie tatsächlich testen möchten .

Wenn Sie versuchen, - Funktionalität auf der konkreten Foo -Implementierung von IFoo zu testen, dann extrahieren Sie eine Schnittstelle und verspotten IFoo wird Ihnen nicht wirklich helfen, da moq die Schnittstelle selbst implementieren wird und nur die "Funktionalität" bereitstellt, die Sie in den Setup-Methoden und Eigenschaften von angeben diese Implementierung. Wenn Sie dies tun, rufen [~ # ~] und [~ # ~] die zu testende Methode eine andere Methode in der konkreten Foo-Implementierung auf , [~ # ~] und [~ # ~] diese Methode ist etwas, für das Sie eine Abstraktion bereitstellen möchten, dann würde ich als @Chris Marisic erwähnte und die Methode Sie ändern Wunsch, zu virtual zu abstrahieren. Mit diesem als virtuell verwenden Sie können ein spöttisches Framework, um die Abstraktion bereitzustellen, oder tun, was ich in der Vergangenheit getan habe, und erstellen einen Untertyp aus dem Testobjekt in der Testklasse.

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

Mit dieser Methode müssen Sie dem Konstruktor von Foo noch etwas zur Verfügung stellen, aber das wäre ein Hauptfall für die Verwendung von Mocks auf diesen Schnittstellen.

Wenn Sie nun Foo testen und die zu testende Methode nur Methoden für IBar, IFizz, IBuzz aufruft, ist dies der Fall Wenn Sie keine andere Abstraktion erstellen möchten, sind Sie bereits eingerichtet. Verspotten Sie diese Schnittstellen, richten Sie sie so ein, dass sie das zurückgeben, was Sie in einem bestimmten Testfall erwarten, und versehen Sie Foo mit den Verspottungsobjekten.

Die Ratschläge zum Extrakt-Interface sind wirklich nützlich, wenn Ihr Testobjekt von Foo abhängig ist, wie von @Chris Marisic erwähnt. Ich kann jedoch seine Ansicht nicht teilen, dass ich in dieser Situation keine Schnittstellen schaffen möchte. Es hängt davon ab, was für Sie mehr riecht: Erstellen einer Schnittstelle, um die Möglichkeit zum Verspotten bereitzustellen, oder Ändern des Modifikators, um dieselbe bereitzustellen. Wenn Sie den Modifikator ändern und dennoch moq verwenden, um das konkrete Foo zu verspotten, müssen Sie entweder einen Standardkonstruktor ( für mich ziemlich übel riechend verfügbar machen =) oder erstellen Sie den Mock mit den angegebenen Konstruktorargumenten, wie in Ihrer referenzierten Antwort erwähnt.

Nehmen wir an, Sie haben Folgendes:

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

Im obigen ist Baz abhängig von Foo. Nach dem Extrahieren einer Schnittstelle von Foo müsste BazIFoo akzeptieren.

Dann würde Foo so aussehen wie in Ihrer Frage und IFoo würde eine Signaturdefinition für die Methode haben, die Sie von Baz abstrahieren möchten.

object GetStuff();

Jetzt würden Sie in Ihrem Test immer noch var foo = new Mock<IFoo>(); verwenden und foo Ihrem Baz Testobjekt geben. Vergessen Sie nicht, die Methode IFoo.GetStuff() einzurichten.

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

Da Sie nun die Abstraktion IFoo erstellt haben, ist dies nicht erforderlich

hier um den Konstruktor herumkommen?

weil der Mock von IFoo nur einen von moq bereitgestellten Standardkonstruktor hat.

Ich persönlich bevorzuge den Schnittstellenansatz, wenn er angewendet wird, weil er IFizz, IBuzz und IBar aus der Abhängigkeitskette von Baz entfernt. Wenn Baz von Foo und Foo von IFizz etc. Abhängt, dann hängt Baz auch von IFizz etc. Nach dem Extrahieren von IFoo hängt Baz nur von IFoo ab.


Ursprüngliche Antwort (keine tatsächliche Antwort # chagrin )

Ich weiß, dass dies vor einiger Zeit gefragt wurde und vielleicht keinen Wert für mich hat, dies jetzt hinzuzufügen, aber für zukünftige Leser sollten wir nicht glauben, dass die folgenden Dinge bei der Verwendung von moq dasselbe sind:

var fooDouble = new Mock<IFoo>();

ist nicht dasselbe wie:

var fooDouble = new Mock<Foo>();

Ich verstehe, dass beim Erstellen eines Mock of IFoo das moq-Framework eine Doppelklasse generiert, die die IFoo-Schnittstelle implementiert und somit den Standardkonstruktor für sich selbst in der generierten Implementierung bereitstellt. Der eigentliche Foo-Mock implementiert die Schnittstelle jedoch nicht direkt im generierten Code, sondern erbt von der konkreten Foo-Klasse und erfordert daher, dass alle Methoden, die gestubbt werden sollen, virtuell gemacht werden, und ob es konkrete Implementierungen gibt, die Sie möchten Wenn Sie das zu testende Gerät tatsächlich verwenden möchten, benötigen Sie "CallBase = true" für Ihr Modell. Wenn Sie dies mit einer konkreten Klasse mit Konstruktorargumenten tun möchten, suchen Sie möglicherweise nach der Antwort von @ Palkin, um zu vermeiden, dass ein Standardkonstruktor verfügbar gemacht wird.

10
Josh Gust

Sie sollten nichts ändern, wenn Sie eine Schnittstelle IFoo haben und die Klasse Foo verspotten möchten, die einen Konstruktor mit Parametern hat.

Ihr Code ist genau das, was Sie brauchen.

var foo = new Mock<IFoo>();

Der folgende Hinweis behandelt die Situationen, in denen die Klasse keine Schnittstelle und keinen parameterlosen Konstruktor hat. Tatsächlich können Sie alle benötigten Parameter an den Konstruktor von Mock übergeben.

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

aber

"Am besten klicken Sie mit der rechten Maustaste auf Ihre Klasse und wählen Schnittstelle extrahieren." (c)

8
Ilya Palkin

Am besten klicken Sie mit der rechten Maustaste auf Ihre Klasse und wählen Sie Schnittstelle extrahieren.

Ich werde dieses Konzept in einem orthogonalen Sinn ansprechen. Ich bin mit dieser Aussage nicht einverstanden, eine Schnittstelle ist nicht die Lösung zur fraglichen Situation.

Zurück zum Text der vorherigen Frage:

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

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

Beachten Sie die Methode, die ich hinzugefügt habe.

Ich glaube, die eigentliche Frage ist, wenn Sie nicht speziell CustomerSyncEngine testen, sondern stattdessen eine Klasse testen, die abhängig von CustomerSyncEngine ist. Nennen wir diese Klasse SuperSyncEngine. Einen Test gegen SuperSyncEngine zu erstellen, wird mühsam, da Sie das gesamte CustomerSyncEngine mit seinen 3 Schnittstellen sowie alle weiteren Abhängigkeiten, die SuperSyncEngine hat, verspotten müssen.

Da der Code, den Sie testen möchten, SuperSyncEngine ist, der von CustomerSyncEngine abhängt, ist eine Schnittstelle hier nicht die Antwort. Sie könnten ICustomerSyncEngine erstellen, aber diese Schnittstelle sollte nicht nur für ein spöttisches Framework erstellt werden. Die bessere Lösung ist, CustomerSyncEngine.MethodWithDependencies In virtuell zu ändern

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

Auf diese Weise können Sie die Methode durch ein spöttisches Framework ersetzen, das die Abhängigkeiten ignoriert, mit denen CustomerSyncEngine geliefert wird.

Wenn Sie diesem Ansatz folgen, benötigen Sie wahrscheinlich einen Standardkonstruktor, der auf CustomerSyncEngine verfügbar gemacht wird, damit er verspottet werden kann. Es ist möglich, dass Sie das umgehen und die Abhängigkeiten mit null oder anderen Werten erfüllen, aber das wäre zusätzliche Arbeit, wenn das Ziel darin besteht, die Reibung zu verringern.

7
Chris Marisic

Dies ist eine Art alter Beitrag, aber ich bin gerade auf ein ähnliches Problem gestoßen (beginnend mit Moq). Falls jemand anderes ein ähnliches Problem hat, hier ist was ich hatte:

class Bar : IBar
{
}

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

    //public methods
}

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

Was ich versuche zu tun, ist Test Manager nicht Foo.

Hier war mein erster Testcode, der einen Fehler verursachte.

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

Der Fehler ist Castle.DynamicProxy.InvalidProxyConstructorArgumentsException : Can not instantiate proxy of class: Something.Models.Foo. Could not find a parameterless constructor.

Beim Lesen der Fehlermeldung versteht Moq nicht, wie man Foo instanziiert, da es keinen parameterlosen Konstruktor gibt und wir Moq nicht sagen, wie man einen mit Parametern instanziiert. Ändern Sie diesen zweiten Abschnitt in:

[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

Wenn du deine FOo-Klasse testest, musst du dich nicht lustig machen. Sie müssen nur die Klassen verspotten, die von der Klasse abhängen, die Sie testen möchten.

Etwas wie:

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

Rufe dann die Methode in foo auf, die du testen willst;)

Wenn die Methode in foo eine Methode in bar/fuzz oder fizz verwendet, sollten Sie die Sintax wie folgt verwenden:

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

Auf diese Weise ruft Ihre foo-Methode beim Aufruf das DoSomething auf und gibt immer 1 zurück;)

3
Oscar Bralo

Schnittstellen sind nützlich, wenn ein Typ mehr als eine Implementierung hat. Negativ bedeutet dies, dass Sie darauf achten sollten, keine Schnittstellen abzuleiten, nur weil Sie Schnittstellen möchten (häufiger Fehler). Positiv zu vermerken ist, dass die verspottete Schnittstelle per Definition die zweite Implementierung ist.

Die Schlussfolgerung lautet also: Wenn ein Typ als Abhängigkeit eines anderen Typs fungiert, ist es ein guter Kandidat, eine Schnittstelle zu implementieren. In diesem Fall können Sie die Schnittstelle in Unit-Tests frei und vollständig verspotten.

Achten Sie beim Definieren einer Schnittstelle darauf, dass Sie der Schnittstelle nur funktionale Teile hinzufügen: Methoden, die definieren, wie das Objekt funktioniert und nicht wie es sieht aus. Eine Schnittstelle mit einer Reihe von Gettern/Setzern bietet keinen Mehrwert für das Design. Dies ist ein ziemlich großer theoretischer Bereich, und dieses winzige Fenster ist nicht der richtige Ort, um mehr darüber zu schreiben.

Um den Zusammenhang mit Ihrer Frage zu klären: Eine verspottete Implementierung sollte das für die Schnittstelle erforderliche Verhalten bieten. Dazu verwenden Sie Funktionen des Mocking-Frameworks. Dies hat nichts mit der konkreten Implementierung der Foo-Klasse zu tun - Sie definieren das spezifische Verhalten des Mock-Objekts.

0
Zoran Horvat