Przypisywanie parametrów out / ref w Moq

293

Czy można przypisać parametr out/ refprzy użyciu Moq (3.0+)?

Patrzyłem na używanie Callback(), ale Action<>nie obsługuje parametrów ref, ponieważ jest oparty na ogólnych. Wolałbym również umieścić ograniczenie ( It.Is) na wejściu refparametru, chociaż mogę to zrobić w wywołaniu zwrotnym.

Wiem, że Rhino Mocks obsługuje tę funkcję, ale projekt, nad którym pracuję, już korzysta z Moq.

Richard Szalay
źródło
4
Pytania i odpowiedzi dotyczą Moq 3. Moq 4.8 ma znacznie ulepszoną obsługę parametrów by-ref, od It.IsAny<T>()podobnego matcher ( ref It.Ref<T>.IsAny) do obsługi konfiguracji .Callback()i .Returns()poprzez niestandardowe typy delegatów pasujące do sygnatury metody. Metody chronione są w równym stopniu obsługiwane. Zobacz np. Moją odpowiedź poniżej .
stakx - nie przyczynia się już
Możesz użyć It.Ref <TValue> .Isany dla dowolnej metody, która używa parametru out. Na przykład: moq.Setup (x => x.Method (out It.Ref <string> .IsAny). Returns (TValue);
MikBTC

Odpowiedzi:

116

Moq wersja 4.8 (lub nowsza) ma znacznie ulepszoną obsługę parametrów by-ref:

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

Ten sam wzór działa dla outparametrów.

It.Ref<T>.IsAny działa również dla C # 7 in parametrów (ponieważ są one również referencjami).

stakx - już nie przyczynia się
źródło
2
to jest rozwiązanie, pozwala ci mieć dowolne dane wejściowe jako ref, dokładnie tak, jak działałoby to dla danych wejściowych bez ref. To naprawdę bardzo miłe ulepszone wsparcie
grathad
5
To samo rozwiązanie jednak nie działa out, prawda?
ATD
1
@ATD częściowo tak. Zadeklaruj delegata za pomocą parametru out i przypisz wartość w wywołaniu zwrotnym za pomocą składni z góry
royalTS
warto wspomnieć, że jeśli funkcja, z której kpisz ma więcej argumentów, sygnatura wywołania zwrotnego powinna być zgodna z tym samym wzorem (nie tylko parametr ref / out)
Yoav Feuerstein
320

W przypadku „out” wydaje mi się, że następujące elementy działają.

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

Zgaduję, że Moq sprawdza wartość „oczekiwanej wartości”, gdy wywołujesz Instalatora i zapamiętuje ją.

Dla ref szukam też odpowiedzi.

Przydatny jest następujący przewodnik Szybki start: https://github.com/Moq/moq4/wiki/Quickstart

Craig Celeste
źródło
7
Myślę, że miałem problem z tym, że nie ma metody przypisywania parametrów out / ref z tej metodySetup
Richard Szalay
1
Nie mam rozwiązania dla przypisania parametru ref. Ten przykład przypisuje wartość „wartości wyjściowej” do „b”. Moq nie wykonuje wyrażenia przekazanego do Instalatora, analizuje je i zdaje sobie sprawę, że podajesz „a” dla wartości wyjściowej, więc patrzy na bieżącą wartość „a” i zapamiętuje ją dla kolejnych wywołań.
Craig Celeste
Zobacz także przykłady out i ref na: code.google.com/p/moq/wiki/QuickStart
TrueWill
9
Nie będzie to dla mnie działać, gdy metoda Mocked interface jest wykonywana w innym zakresie, który ma swoją własną zmienną wyjściową (na przykład w metodzie innej klasy). Podany powyżej przykład jest wygodny, ponieważ wykonanie odbywa się w tym samym zakresie co próbna konfiguracja, jednak rozwiązanie wszystkich scenariuszy jest zbyt proste. Obsługa jawnej obsługi wartości out / ref jest słaba w moq (jak powiedział ktoś inny, obsługiwany w czasie wykonywania).
John K
2
+1: jest to pomocna odpowiedź. Ale: jeśli typ parametru out jest klasą, a nie typem wbudowanym typu string - nie sądzę, aby to zadziałało. Próbowałem dzisiaj. Obiekt próbny symuluje wywołanie i zwraca wartość null za pomocą parametru „out”.
azheglov
86

EDYCJA : W Moq 4.10 możesz teraz przekazać delegata, który ma parametr out lub ref bezpośrednio do funkcji Callback:

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

Będziesz musiał zdefiniować delegata i utworzyć go:

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

Dla wersji Moq przed 4.10:

Avner Kashtan udostępnia na swoim blogu metodę rozszerzenia, która pozwala ustawić parametr wyjściowy z wywołania zwrotnego: Moq, Callback i parametry Out: szczególnie trudny przypadek krawędzi

Rozwiązanie jest zarówno eleganckie, jak i zuchwałe. Elegancki, ponieważ zapewnia płynną składnię, która sprawia wrażenie domowej z innymi wywołaniami zwrotnymi Moq. I hacky, ponieważ polega na wywołaniu niektórych wewnętrznych interfejsów API Moq poprzez refleksję.

Metoda rozszerzenia podana w powyższym linku nie została dla mnie skompilowana, dlatego podałem poniżej wersję edytowaną. Musisz utworzyć podpis dla każdej liczby parametrów wejściowych, które masz; Podałem 0 i 1, ale dalsze rozszerzenie powinno być proste:

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

Dzięki powyższej metodzie rozszerzenia możesz przetestować interfejs bez parametrów, takich jak:

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

.. z następującą konfiguracją Moq:

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



Edycja : Aby obsługiwać metody void-return, wystarczy dodać nowe metody przeciążenia:

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

Umożliwia to testowanie interfejsów takich jak:

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);
}
Scott Wegner
źródło
5
@ Wilbert, zaktualizowałem swoją odpowiedź o dodatkowe przeciążenia dla funkcji void-return.
Scott Wegner,
2
Korzystałem z tego rozwiązania w naszym pakiecie testowym i działałem. Jednak od czasu aktualizacji do Moq 4.10 już tego nie robi.
Ristogod
2
Wygląda na to, że został uszkodzony w tym zatwierdzeniu github.com/moq/moq4/commit/… . Może jest teraz lepszy sposób na zrobienie tego?
świeca zapłonowa
1
fyi w Moq4 MethodCall jest teraz własnością setupu, więc wnętrzności OutCallbackInternal powyżej zmieniają się navar 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, dzięki aktualizacji do Moq 4.10, możesz teraz przekazać delegata, który ma parametr out lub ref bezpośrednio do funkcji oddzwaniania: mock.Setup(x=>x.Method(out d)).Callback(myDelegate).Returns(...);Musisz zdefiniować delegata i utworzyć go:...Callback(new MyDelegate((out decimal v)=>v=12m))...;
esteuart
48

To jest dokumentacja ze strony Moq :

// 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);
Kosau
źródło
5
Jest to w zasadzie to samo, co odpowiedź Parched i ma takie samo ograniczenie, ponieważ nie może zmienić wartości wyjściowej w zależności od danych wejściowych ani nie może reagować na parametry ref.
Richard Szalay
@Richard Szalay, możesz, ale musisz mieć osobne ustawienia z osobnymi parametrami „outString”
Sielu,
17

Wydaje się, że nie jest to możliwe po wyjęciu z pudełka. Wygląda na to, że ktoś próbował rozwiązać problem

Zobacz ten post na forum http://code.google.com/p/moq/issues/detail?id=176

to pytanie Zweryfikuj wartość parametru odniesienia za pomocą Moq

Gishu
źródło
Dziękuję za potwierdzenie. Właściwie znalazłem te dwa linki podczas wyszukiwania, ale zauważyłem również, że Moq wymienia jedną z jego funkcji jako „obsługę parametrów ref / out”, więc chciałem się upewnić.
Richard Szalay
3

Aby zwrócić wartość wraz z ustawieniem parametru ref, oto fragment kodu:

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

Następnie zadeklaruj własnego delegata pasującego do podpisu metody, która ma być wyśmiewana i podaj własną implementację metody.

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;
         }));
    }
Victor Mukherjee
źródło
Czy to działa, gdy nie masz dostępu do zmiennej, która będzie przekazywana jako y? Mam funkcję, która pobiera dwa argumenty odwołania do DayOfWeek. Muszę ustawić oba z nich na określony dzień w próbnym kodzie pośredniczącym, a trzeci argument to próbny kontekst bazy danych. Ale metoda delegowania po prostu nie jest wywoływana. Wygląda na to, że Moq spodziewałby się, że dopasuje lokalny parametr przekazywany do funkcji „MyMethod”. Czy tak to działa na twoim przykładzie? Dzięki.
Greg Veres
3

Opierając się na programie Billy Jakes, stworzyłem w pełni dynamiczną próbną metodę z parametrem out. Zamieszczam to tutaj dla każdego, kto uzna to za przydatne (prawdopodobnie tylko mnie w przyszłości).

// 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);
Martijn
źródło
2

Jestem pewien, że rozwiązanie Scotta zadziałało w pewnym momencie,

Ale to dobry argument za tym, by nie używać refleksji do podglądania prywatnych apis. Teraz jest zepsute.

Byłem w stanie ustawić parametry za pomocą delegata

      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);
    }
Billy Jake O'Connor
źródło
1

To może być rozwiązanie.

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

}
Fabrice
źródło
1
Jest to w zasadzie to samo, co odpowiedź Parched i ma takie samo ograniczenie, ponieważ nie może zmienić wartości wyjściowej w zależności od danych wejściowych ani nie może reagować na parametry ref.
Richard Szalay
1

Zmagałem się z wieloma sugestiami tutaj, zanim po prostu utworzyłem instancję nowej „fałszywej” klasy, która implementuje interfejs, który próbujesz wyszydzić. Następnie możesz po prostu ustawić wartość parametru out za pomocą samej metody.

Casey O'Brien
źródło
0

Walczyłem z tym przez godzinę po południu i nigdzie nie mogłem znaleźć odpowiedzi. Po samodzielnej zabawie udało mi się wymyślić rozwiązanie, które działało dla mnie.

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

Kluczem jest tutaj, mock.SetupAllProperties();który usunie wszystkie właściwości dla ciebie. To nie może działać w każdym scenariuszu testowym, ale jeśli wszystko czego dbają o jest uzyskiwanie return valueod YourMethodówczesnego tej pracy grzywny.

maxshuty
źródło