Assegnare parametri out / ref in Moq


293

È possibile assegnare un out/ref parametro usando Moq (3.0+)?

Ho esaminato l'utilizzo Callback(), ma Action<>non supporta i parametri ref perché si basa sui generici. Preferirei anche mettere un vincolo ( It.Is) sull'input del refparametro, anche se posso farlo nel callback.

So che Rhino Mocks supporta questa funzionalità, ma il progetto a cui sto lavorando sta già utilizzando Moq.


4
Questa domanda e risposta riguarda Moq 3. Moq 4.8 ha un supporto molto migliorato per i parametri by-ref, che vanno da un It.IsAny<T>()matcher ( ref It.Ref<T>.IsAny) simile al supporto per l'impostazione .Callback()e .Returns()tramite un tipo di delegato personalizzato corrispondente alla firma del metodo. I metodi protetti sono ugualmente supportati. Vedi ad esempio la mia risposta di seguito .
stakx - non contribuisce più l'

È possibile utilizzare It.Ref <TValue> .Isany per qualsiasi metodo che utilizza il parametro out. Ad esempio: moq.Setup (x => x.Method (out It.Ref <string> .IsAny) .Returns (TValue);
MikBTC

Risposte:


117

Moq versione 4.8 (o successive) ha un supporto molto migliorato per i parametri 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);
}

Lo stesso modello funziona per i outparametri.

It.Ref<T>.IsAnyfunziona anche per i inparametri C # 7 (poiché sono anche by-ref).


2
questa è la soluzione, questo ti permette di avere qualsiasi input come ref, esattamente come funzionerebbe per l'input non ref. Questo è davvero un bel supporto migliorato
grathad

5
Questa stessa soluzione non funziona outperò, vero?
ATD

1
@ATD in parte sì. Dichiara un delegato con il parametro out e assegna il valore nel callback con la sintassi dall'alto
royalTS

vale la pena ricordare che se la funzione che stai deridendo ha più argomenti, la firma di callback dovrebbe seguire lo stesso modello (non solo il parametro ref / out)
Yoav Feuerstein

320

Per "fuori", il seguente sembra funzionare per me.

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

Immagino che Moq guardi il valore di "expectedValue" quando si chiama Setup e se lo ricorda.

Per ref , sto cercando anche una risposta.

Ho trovato utile la seguente guida QuickStart: https://github.com/Moq/moq4/wiki/Quickstart


7
Penso che il problema che ho avuto fosse che non esiste un metodo per assegnare i parametri out / ref dal metodoSetup
Richard Szalay,

1
Non ho una soluzione per assegnare un parametro ref. In questo esempio viene assegnato un valore di "valore di output" a "b". Moq non esegue l'espressione che passi al programma di installazione, lo analizza e si rende conto che stai fornendo 'a' per un valore di output, quindi esamina il valore attuale di 'a' e lo ricorda per le chiamate successive.
Craig Celeste,

Vedi anche gli esempi out e ref su: code.google.com/p/moq/wiki/QuickStart
TrueWill

9
Questo non funzionerà per me quando il metodo dell'interfaccia derisa viene eseguito in un ambito diverso che ha una propria variabile di output referenziata (ad esempio all'interno del metodo di un'altra classe). L'esempio sopra riportato è conveniente perché l'esecuzione avviene nello stesso ambito di l'installazione finta, tuttavia è troppo semplice per risolvere tutti gli scenari. Il supporto per la gestione esplicita del valore out / ref è debole in moq (come detto da qualcun altro, gestito al momento dell'esecuzione).
John K,

2
+1: questa è una risposta utile. Ma: se il tipo di parametro out è una classe piuttosto che un tipo incorporato come stringa - non credo che funzionerà. Ho provato oggi. L'oggetto simulato simula la chiamata e restituisce un valore null tramite il parametro "out".
Azheglov,

86

EDIT : In Moq 4.10, ora puoi passare un delegato che ha un parametro out o ref direttamente alla funzione Callback:

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

Dovrai definire un delegato e istanziarlo:

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

Per la versione Moq precedente alla 4.10:

Avner Kashtan fornisce un metodo di estensione nel suo blog che consente di impostare il parametro out da un callback: parametri Moq, Callbacks e Out: un caso particolarmente complicato

La soluzione è allo stesso tempo elegante e confusa. Elegante in quanto fornisce una sintassi fluida che si sente a casa con altri callback Moq. E hacky perché si basa sulla chiamata di alcune API Moq interne tramite la riflessione.

Il metodo di estensione fornito al link sopra non è stato compilato per me, quindi ho fornito una versione modificata di seguito. Dovrai creare una firma per ogni numero di parametri di input che hai; Ho fornito 0 e 1, ma estenderlo ulteriormente dovrebbe essere semplice:

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

Con il metodo di estensione sopra, puoi testare un'interfaccia senza parametri come:

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

.. con la seguente configurazione 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);
    }



Modifica : per supportare i metodi void-return, devi semplicemente aggiungere nuovi metodi di overload:

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

Ciò consente di testare interfacce come:

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, ho aggiornato la mia risposta con sovraccarichi aggiuntivi per le funzioni di ritorno del vuoto.
Scott Wegner,

2
Ho usato questa soluzione nella nostra suite di test e avevo funzionato. Tuttavia, dall'aggiornamento a Moq 4.10, non funziona più.
Ristogod,

2
Sembra che sia stato rotto in questo commit github.com/moq/moq4/commit/… . Forse c'è un modo migliore per farlo ora?
candela

1
A proposito di Moq4, MethodCall è ora una proprietà dell'installazione, quindi il coraggio di OutCallbackInternal sopra cambia invar 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, con l'aggiornamento a Moq 4.10, ora puoi passare un delegato che ha un parametro out o ref direttamente alla funzione Callback: mock.Setup(x=>x.Method(out d)).Callback(myDelegate).Returns(...);dovrai definire un delegato e istanziarlo:...Callback(new MyDelegate((out decimal v)=>v=12m))...;
esteuart

48

Questa è la documentazione dal sito 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);

5
Questo è fondamentalmente lo stesso della risposta di Parched e ha la stessa limitazione, in quanto non può cambiare il valore di uscita a seconda dell'input né può rispondere ai parametri di riferimento.
Richard Szalay,

@Richard Szalay, potresti, ma dovresti avere configurazioni separate con parametri "outString" separati
Sielu,

17

Sembra che non sia possibile fuori dalla scatola. Sembra che qualcuno abbia tentato una soluzione

Vedi questo post sul forum http://code.google.com/p/moq/issues/detail?id=176

questa domanda Verificare il valore del parametro di riferimento con Moq


Grazie per la conferma. In realtà avevo trovato quei due collegamenti nella mia ricerca, ma ho anche notato che Moq elenca una delle sue caratteristiche come "supporto parametri ref / out", quindi volevo esserne sicuro.
Richard Szalay,

3

Per restituire un valore insieme all'impostazione del parametro ref, ecco un pezzo di codice:

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

Quindi dichiarare il proprio delegato corrispondente alla firma del metodo da beffare e fornire l'implementazione del proprio metodo.

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

Funziona quando non hai accesso alla variabile che verrà passata come y? Ho una funzione che accetta due argomenti ref su DayOfWeek. Devo impostare entrambi questi in un giorno particolare nello stub finto e il terzo argomento è un contesto di database finto. Ma il metodo delegato non viene chiamato. Sembra che Moq si aspetterebbe di abbinare il y locale passato alla tua funzione "MyMethod". È così che funziona per il tuo esempio? Grazie.
Greg Veres,

3

Basandomi sul tendalino Billy Jakes, ho realizzato un metodo simulato completamente dinamico con un parametro out. Sto pubblicando questo qui per chiunque lo ritenga utile (probabilmente solo per il futuro).

// 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

Sono sicuro che la soluzione di Scott ha funzionato a un certo punto,

Ma è un buon argomento per non usare la riflessione per sbirciare le API private. Adesso è rotto.

Sono stato in grado di impostare i parametri utilizzando un delegato

      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

Questa può essere una soluzione.

[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
Questo è fondamentalmente lo stesso della risposta di Parched e ha la stessa limitazione, in quanto non può cambiare il valore di uscita a seconda dell'input né può rispondere ai parametri di riferimento.
Richard Szalay,

1

Ho lottato con molti dei suggerimenti qui prima di creare semplicemente un'istanza di una nuova classe "Fake" che implementa qualsiasi interfaccia che stai cercando di deridere. Quindi puoi semplicemente impostare il valore del parametro out con il metodo stesso.


0

Ho lottato con questo per un'ora questo pomeriggio e non sono riuscito a trovare una risposta da nessuna parte. Dopo aver giocato da solo con esso sono stato in grado di trovare una soluzione che ha funzionato per me.

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

La chiave qui è mock.SetupAllProperties();che stub tutte le proprietà per te. Questo potrebbe non funzionare in tutti i casi scenario di test, ma se tutto quello che interessa è ottenere il return valuedi YourMethodallora questo lavoro sarà bene.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.