Zuweisen von Out / Ref-Parametern in Moq


293

Ist es möglich, einen out/ ref-Parameter mit Moq (3.0+) zuzuweisen?

Ich habe mir die Verwendung angesehen Callback(), unterstütze jedoch Action<>keine Ref-Parameter, da diese auf Generika basieren. Ich möchte auch vorzugsweise eine Einschränkung ( It.Is) für die Eingabe des refParameters setzen, obwohl ich dies im Rückruf tun kann.

Ich weiß, dass Rhino Mocks diese Funktionalität unterstützt, aber das Projekt, an dem ich arbeite, verwendet bereits Moq.


4
In dieser Frage und Antwort geht es um Moq 3. Moq 4.8 bietet eine deutlich verbesserte Unterstützung für By-Ref-Parameter, angefangen von einem It.IsAny<T>()ähnlichen Matcher ( ref It.Ref<T>.IsAny) bis hin zur Unterstützung beim Einrichten .Callback()und .Returns()über benutzerdefinierte Delegattypen, die mit der Methodensignatur übereinstimmen. Geschützte Methoden werden gleichermaßen unterstützt. Siehe zB meine Antwort unten .
stakx - nicht mehr beitragen

Sie können It.Ref <TValue> .Isany für jede Methode verwenden, die out-Parameter verwendet. Zum Beispiel: moq.Setup (x => x.Method (out It.Ref <string> .IsAny) .Returns (TValue);
MikBTC

Antworten:


117

Moq Version 4.8 (oder höher) bietet eine deutlich verbesserte Unterstützung für By-Ref-Parameter:

public interface IGobbler
{
    bool Gobble(ref int amount);
}

delegate void GobbleCallback(ref int amount);     // needed for Callback
delegate bool GobbleReturns(ref int amount);      // needed for Returns

var mock = new Mock<IGobbler>();
mock.Setup(m => m.Gobble(ref It.Ref<int>.IsAny))  // match any value passed by-ref
    .Callback(new GobbleCallback((ref int amount) =>
     {
         if (amount > 0)
         {
             Console.WriteLine("Gobbling...");
             amount -= 1;
         }
     }))
    .Returns(new GobbleReturns((ref int amount) => amount > 0));

int a = 5;
bool gobbleSomeMore = true;
while (gobbleSomeMore)
{
    gobbleSomeMore = mock.Object.Gobble(ref a);
}

Das gleiche Muster gilt für outParameter.

It.Ref<T>.IsAnyfunktioniert auch für C # 7- inParameter (da sie auch by-ref sind).


2
Dies ist die Lösung, mit der Sie jede Eingabe als Referenz haben können, genau so, wie es für eine Eingabe ohne Referenz funktionieren würde. Dies ist in der Tat eine sehr schöne verbesserte Unterstützung
grathad

5
Dieselbe Lösung funktioniert jedoch nicht out, oder?
ATD

1
@ATD teilweise ja. Deklarieren Sie einen Delegaten mit dem Parameter out und weisen Sie den Wert im Rückruf mit der Syntax von oben zu
royalTS

Erwähnenswert ist, dass, wenn die Funktion, die Sie verspotten, mehr Argumente enthält, die Rückrufsignatur dem gleichen Muster folgen sollte (nicht nur dem Parameter ref / out)
Yoav Feuerstein,

320

Für 'out' scheint das Folgende für mich zu funktionieren.

public interface IService
{
    void DoSomething(out string a);
}

[TestMethod]
public void Test()
{
    var service = new Mock<IService>();
    var expectedValue = "value";
    service.Setup(s => s.DoSomething(out expectedValue));

    string actualValue;
    service.Object.DoSomething(out actualValue);
    Assert.AreEqual(expectedValue, actualValue);
}

Ich vermute, dass Moq beim Aufrufen von Setup den Wert von 'expectedValue' überprüft und sich daran erinnert.

Denn refich suche auch nach einer Antwort.

Ich fand die folgende Kurzanleitung hilfreich: https://github.com/Moq/moq4/wiki/Quickstart


7
Ich denke, das Problem, das ich hatte, war, dass es keine Methode gibt , die out / ref-Parameter aus der Methode zuzuweisenSetup
Richard Szalay

1
Ich habe keine Lösung für die Zuweisung eines ref-Parameters. In diesem Beispiel wird 'b' der Wert "Ausgabewert" zugewiesen. Moq führt den Ausdruck, den Sie an Setup übergeben, nicht aus, sondern analysiert ihn und stellt fest, dass Sie 'a' für einen Ausgabewert angeben. Daher wird der aktuelle Wert von 'a' angezeigt und für nachfolgende Aufrufe gespeichert.
Craig Celeste

Siehe auch die Out-
TrueWill

9
Dies funktioniert bei mir nicht, wenn die Mocked-Schnittstellenmethode in einem anderen Bereich ausgeführt wird, der über eine eigene referenzierte Ausgabevariable verfügt (z. B. innerhalb der Methode einer anderen Klasse). Das oben angegebene Beispiel ist praktisch, da die Ausführung im selben Bereich wie erfolgt Das Mock-Setup ist jedoch zu einfach, um alle Szenarien zu lösen. Die Unterstützung für die explizite Behandlung des out / ref-Werts ist in moq schwach (wie von jemand anderem gesagt, zur Ausführungszeit behandelt).
John K

2
+1: Dies ist eine hilfreiche Antwort. Aber: Wenn der out-Parametertyp eher eine Klasse als ein eingebauter Typ wie ein String ist, glaube ich nicht, dass dies funktionieren wird. Versuchte es heute. Das Scheinobjekt simuliert den Aufruf und gibt über den Parameter "out" eine Null zurück.
Azheglov

86

BEARBEITEN : In Moq 4.10 können Sie jetzt einen Delegaten mit einem out- oder ref-Parameter direkt an die Callback-Funktion übergeben:

mock
  .Setup(x=>x.Method(out d))
  .Callback(myDelegate)
  .Returns(...); 

Sie müssen einen Delegaten definieren und instanziieren:

...
.Callback(new MyDelegate((out decimal v)=>v=12m))
...

Für die Moq-Version vor 4.10:

Avner Kashtan bietet in seinem Blog eine Erweiterungsmethode an, mit der der out-Parameter aus einem Rückruf festgelegt werden kann: Moq-, Callbacks- und Out-Parameter: ein besonders schwieriger Randfall

Die Lösung ist sowohl elegant als auch hackig. Elegant, da es eine fließende Syntax bietet, die sich bei anderen Moq-Rückrufen wie zu Hause fühlt. Und hacky, weil es darauf beruht, einige interne Moq-APIs über Reflection aufzurufen.

Die unter dem obigen Link angegebene Erweiterungsmethode wurde für mich nicht kompiliert, daher habe ich unten eine bearbeitete Version bereitgestellt. Sie müssen für jede Anzahl von Eingabeparametern eine Signatur erstellen. Ich habe 0 und 1 angegeben, aber die weitere Erweiterung sollte einfach sein:

public static class MoqExtensions
{
    public delegate void OutAction<TOut>(out TOut outVal);
    public delegate void OutAction<in T1,TOut>(T1 arg1, out TOut outVal);

    public static IReturnsThrows<TMock, TReturn> OutCallback<TMock, TReturn, TOut>(this ICallback<TMock, TReturn> mock, OutAction<TOut> action)
        where TMock : class
    {
        return OutCallbackInternal(mock, action);
    }

    public static IReturnsThrows<TMock, TReturn> OutCallback<TMock, TReturn, T1, TOut>(this ICallback<TMock, TReturn> mock, OutAction<T1, TOut> action)
        where TMock : class
    {
        return OutCallbackInternal(mock, action);
    }

    private static IReturnsThrows<TMock, TReturn> OutCallbackInternal<TMock, TReturn>(ICallback<TMock, TReturn> mock, object action)
        where TMock : class
    {
        mock.GetType()
            .Assembly.GetType("Moq.MethodCall")
            .InvokeMember("SetCallbackWithArguments", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, mock,
                new[] { action });
        return mock as IReturnsThrows<TMock, TReturn>;
    }
}

Mit der obigen Erweiterungsmethode können Sie eine Schnittstelle ohne Parameter testen, z.

public interface IParser
{
    bool TryParse(string token, out int value);
}

.. mit folgendem Moq-Setup:

    [TestMethod]
    public void ParserTest()
    {
        Mock<IParser> parserMock = new Mock<IParser>();

        int outVal;
        parserMock
            .Setup(p => p.TryParse("6", out outVal))
            .OutCallback((string t, out int v) => v = 6)
            .Returns(true);

        int actualValue;
        bool ret = parserMock.Object.TryParse("6", out actualValue);

        Assert.IsTrue(ret);
        Assert.AreEqual(6, actualValue);
    }



Bearbeiten : Um Void-Return-Methoden zu unterstützen, müssen Sie lediglich neue Überladungsmethoden hinzufügen:

public static ICallbackResult OutCallback<TOut>(this ICallback mock, OutAction<TOut> action)
{
    return OutCallbackInternal(mock, action);
}

public static ICallbackResult OutCallback<T1, TOut>(this ICallback mock, OutAction<T1, TOut> action)
{
    return OutCallbackInternal(mock, action);
}

private static ICallbackResult OutCallbackInternal(ICallback mock, object action)
{
    mock.GetType().Assembly.GetType("Moq.MethodCall")
        .InvokeMember("SetCallbackWithArguments", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, mock, new[] { action });
    return (ICallbackResult)mock;
}

Dies ermöglicht das Testen von Schnittstellen wie:

public interface IValidationRule
{
    void Validate(string input, out string message);
}

[TestMethod]
public void ValidatorTest()
{
    Mock<IValidationRule> validatorMock = new Mock<IValidationRule>();

    string outMessage;
    validatorMock
        .Setup(v => v.Validate("input", out outMessage))
        .OutCallback((string i, out string m) => m  = "success");

    string actualMessage;
    validatorMock.Object.Validate("input", out actualMessage);

    Assert.AreEqual("success", actualMessage);
}

5
@ Wilbert, ich habe meine Antwort mit zusätzlichen Überladungen für ungültige Rückgabefunktionen aktualisiert.
Scott Wegner

2
Ich habe diese Lösung in unserer Testsuite verwendet und gearbeitet. Seit der Aktualisierung auf Moq 4.10 ist dies jedoch nicht mehr der Fall.
Ristogod

2
Es sieht so aus, als ob es in diesem Commit gebrochen wurde. Github.com/moq/moq4/commit/… . Vielleicht gibt es jetzt einen besseren Weg, dies zu tun?
Zündkerze

1
fyi in Moq4 ist der MethodCall jetzt eine Eigenschaft des Setups, so dass sich der Mut von OutCallbackInternal oben zuvar methodCall = mock.GetType().GetProperty("Setup").GetValue(mock); mock.GetType().Assembly.GetType("Moq.MethodCall") .InvokeMember("SetCallbackResponse", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, methodCall, new[] { action });
mike mckechnie

1
@Ristogod, mit dem Update auf Moq 4.10 können Sie jetzt einen Delegaten mit einem out- oder ref-Parameter direkt an die Rückruffunktion übergeben: mock.Setup(x=>x.Method(out d)).Callback(myDelegate).Returns(...);Sie müssen einen Delegaten definieren und instanziieren:...Callback(new MyDelegate((out decimal v)=>v=12m))...;
estuart

48

Dies ist eine Dokumentation von der Moq-Site :

// out arguments
var outString = "ack";
// TryParse will return true, and the out argument will return "ack", lazy evaluated
mock.Setup(foo => foo.TryParse("ping", out outString)).Returns(true);


// ref arguments
var instance = new Bar();
// Only matches if the ref argument to the invocation is the same instance
mock.Setup(foo => foo.Submit(ref instance)).Returns(true);

5
Dies ist im Grunde das Gleiche wie die Antwort von Parched und hat die gleiche Einschränkung, da es den Out-Wert nicht abhängig von der Eingabe ändern oder auf ref-Parameter reagieren kann.
Richard Szalay

@ Richard Szalay, Sie könnten, aber Sie müssten separate Setups mit separaten "outString" -Parametern haben
Sielu

17

Scheint, als sei es nicht sofort möglich. Sieht so aus, als hätte jemand eine Lösung versucht

Siehe diesen Forumsbeitrag http://code.google.com/p/moq/issues/detail?id=176

diese Frage Überprüfen Sie den Wert des Referenzparameters mit Moq


Danke für die Bestätigung. Ich hatte diese beiden Links tatsächlich bei meiner Suche gefunden, aber auch festgestellt, dass Moq eine seiner Funktionen als "unterstützende Ref / Out-Parameter" auflistet, also wollte ich sicher sein.
Richard Szalay

3

Um einen Wert zusammen mit dem Einstellen des Ref-Parameters zurückzugeben, finden Sie hier einen Code:

public static class MoqExtensions
{
    public static IReturnsResult<TMock> DelegateReturns<TMock, TReturn, T>(this IReturnsThrows<TMock, TReturn> mock, T func) where T : class
        where TMock : class
    {
        mock.GetType().Assembly.GetType("Moq.MethodCallReturn`2").MakeGenericType(typeof(TMock), typeof(TReturn))
            .InvokeMember("SetReturnDelegate", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, mock,
                new[] { func });
        return (IReturnsResult<TMock>)mock;
    }
}

Deklarieren Sie dann Ihren eigenen Delegaten, der mit der Signatur der zu verspottenden Methode übereinstimmt, und stellen Sie Ihre eigene Methodenimplementierung bereit.

public delegate int MyMethodDelegate(int x, ref int y);

    [TestMethod]
    public void TestSomething()
    {
        //Arrange
        var mock = new Mock<ISomeInterface>();
        var y = 0;
        mock.Setup(m => m.MyMethod(It.IsAny<int>(), ref y))
        .DelegateReturns((MyMethodDelegate)((int x, ref int y)=>
         {
            y = 1;
            return 2;
         }));
    }

Funktioniert dies, wenn Sie keinen Zugriff auf die Variable haben, die als y übergeben werden soll? Ich habe eine Funktion, die zwei ref-Argumente für DayOfWeek verwendet. Ich muss beide auf einen bestimmten Tag im Mock-Stub setzen, und das dritte Argument ist ein Mock-Datenbankkontext. Die Delegate-Methode wird jedoch nicht aufgerufen. Es sieht so aus, als würde Moq erwarten, dass es mit dem lokalen y übereinstimmt, das von Ihrer "MyMethod" -Funktion übergeben wird. Funktioniert das für Ihr Beispiel so? Vielen Dank.
Greg Veres

3

Aufbauend auf Billy Jakes awnser habe ich eine volldynamische Mock-Methode mit einem out-Parameter erstellt. Ich poste dies hier für jeden, der es nützlich findet (wahrscheinlich nur für mich in der Zukunft).

// Define a delegate with the params of the method that returns void.
delegate void methodDelegate(int x, out string output);

// Define a variable to store the return value.
bool returnValue;

// Mock the method: 
// Do all logic in .Callback and store the return value.
// Then return the return value in the .Returns
mockHighlighter.Setup(h => h.SomeMethod(It.IsAny<int>(), out It.Ref<int>.IsAny))
  .Callback(new methodDelegate((int x, out int output) =>
  {
    // do some logic to set the output and return value.
    output = ...
    returnValue = ...
  }))
  .Returns(() => returnValue);

2

Ich bin sicher, dass Scotts Lösung an einem Punkt funktioniert hat.

Aber es ist ein gutes Argument dafür, keine Reflexion zu verwenden, um einen Blick auf private Apis zu werfen. Es ist jetzt kaputt.

Ich konnte Parameter mit einem Delegaten festlegen

      delegate void MockOutDelegate(string s, out int value);

    public void SomeMethod()
    {
        ....

         int value;
         myMock.Setup(x => x.TryDoSomething(It.IsAny<string>(), out value))
            .Callback(new MockOutDelegate((string s, out int output) => output = userId))
            .Returns(true);
    }

1

Dies kann eine Lösung sein.

[Test]
public void TestForOutParameterInMoq()
{
  //Arrange
  _mockParameterManager= new Mock<IParameterManager>();

  Mock<IParameter > mockParameter= new Mock<IParameter >();
  //Parameter affectation should be useless but is not. It's really used by Moq 
  IParameter parameter= mockParameter.Object;

  //Mock method used in UpperParameterManager
  _mockParameterManager.Setup(x => x.OutMethod(out parameter));

  //Act with the real instance
  _UpperParameterManager.UpperOutMethod(out parameter);

  //Assert that method used on the out parameter of inner out method are really called
  mockParameter.Verify(x => x.FunctionCalledInOutMethodAfterInnerOutMethod(),Times.Once());

}

1
Dies ist im Grunde das Gleiche wie die Antwort von Parched und hat die gleiche Einschränkung, da es den Out-Wert nicht abhängig von der Eingabe ändern oder auf ref-Parameter reagieren kann.
Richard Szalay

1

Ich hatte mit vielen der Vorschläge hier zu kämpfen, bevor ich einfach eine Instanz einer neuen 'Fake'-Klasse erstellte, die jede Schnittstelle implementiert, die Sie verspotten möchten. Dann können Sie einfach den Wert des out-Parameters mit der Methode selbst festlegen.


0

Ich hatte heute Nachmittag eine Stunde damit zu kämpfen und konnte nirgendwo eine Antwort finden. Nachdem ich alleine damit herumgespielt hatte, konnte ich eine Lösung finden, die für mich funktionierte.

string firstOutParam = "first out parameter string";
string secondOutParam = 100;
mock.SetupAllProperties();
mock.Setup(m=>m.Method(out firstOutParam, out secondOutParam)).Returns(value);

Der Schlüssel hier ist, mock.SetupAllProperties();der alle Eigenschaften für Sie auslöscht. Dies funktioniert möglicherweise nicht in jedem Testfallszenario, aber wenn Sie sich nur darum kümmern, das return valuezu erhalten YourMethod, funktioniert dies einwandfrei.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.