Potete aiutarmi a capire Moq Callback?


95

Utilizzo Moq e guardato Callbackma non sono riuscito a trovare un semplice esempio per capire come usarlo.

Hai un piccolo frammento di lavoro che spiega chiaramente come e quando usarlo?

Risposte:


83

Difficile da battere https://github.com/Moq/moq4/wiki/Quickstart

Se non è abbastanza chiaro, lo chiamerei un bug doc ...

EDIT: In risposta al tuo chiarimento ...

Per ogni metodo deriso Setupche esegui, devi indicare cose come:

  • vincoli sugli input
  • il valore per / modo in cui deve essere derivato il valore restituito (se presente)

Il .Callbackmeccanismo dice "Non posso descriverlo in questo momento, ma quando si verifica una chiamata in questo modo, richiamami e farò ciò che deve essere fatto". Come parte della stessa catena di chiamate fluente, puoi controllare il risultato da restituire (se presente) tramite .Returns". Negli esempi QS, un esempio è che fanno aumentare il valore restituito ogni volta.

In generale, non avrai bisogno di un meccanismo come questo molto spesso (xUnit Test Patterns ha termini per antipatterns del genere Conditional Logic In Tests), e se c'è un modo più semplice o integrato per stabilire ciò di cui hai bisogno, dovrebbe essere usato di preferenza.

La parte 3 di 4 nella serie Moq di Justin Etheredge lo copre, e c'è un altro esempio di callback qui

Un semplice esempio di callback può essere trovato in Using Callbacks with Moq post.


3
Ciao Ruben, sto imparando Moq e se vuoi sto costruendo molti esempi per capire come fare le cose usandolo. Il mio problema è che non capisco quando usarlo.Una volta capito che il problema risolto scriverò il mio codice.Se dovessi spiegarlo con parole tue quando useresti il ​​callback? grazie apprezziamo il tuo tempo
user9969

15
Difficile da battere [link]? Affatto. Quel collegamento ti mostra come fare dozzine di cose diverse, ma non ti dice perché dovresti fare qualcuna di esse. Che è un problema comune nella documentazione beffarda, ho scoperto. Posso contare su zero dita il numero di buone e chiare spiegazioni di TDD + derisione che ho trovato. La maggior parte presume un livello di conoscenza che, se lo avessi, non avrei bisogno di leggere l'articolo.
Ryan Lundy

@Kyralessa: capisco il tuo punto. Personalmente avevo un bel po 'di conoscenza dei libri in arrivo, quindi ho trovato la roba di avvio rapido assolutamente perfetta. Purtroppo non sono a conoscenza di un esempio migliore di quelli a cui ho linkato alla fine del post. Se ne trovi uno, pubblicalo qui e sarò felice di modificarlo in (o sentiti libero di fare il fai-da-te)
Ruben Bartelink

"Farò ciò che deve essere fatto e ti dirò il risultato da restituire (se presente)" Penso che questo sia fuorviante, AFAIU Callbacknon ha nulla a che fare con il valore di ritorno (a meno che non ti capiti di collegarlo tramite codice). Fondamentalmente si assicura solo che la richiamata venga chiamata prima o dopo ogni invocazione (a seconda che sia stata concatenata prima o dopo Returnsrispettivamente), semplice e semplice.
Ohad Schneider

1
@OhadSchneider Seguendo il mio link ... hai ragione! Mi chiedo (ma non mi interessa abbastanza dato che non ho usato Moq per moooolto tempo) se l'interfaccia di Fluent è cambiata (non sembra probabile, cioè ho fatto un'ipotesi sbagliata e non ho letto la cosa a cui ho collegato come normalmente l'avrei risolta dall'autocompletamento me stesso). Spero che la correzione
risolva il

59

Ecco un esempio di utilizzo di una richiamata per testare un'entità inviata a un servizio dati che gestisce un inserimento.

var mock = new Mock<IDataService>();
DataEntity insertedEntity = null;

mock.Setup(x => x.Insert(It.IsAny<DataEntity>())).Returns(1) 
           .Callback((DataEntity de) => insertedEntity = de);

Sintassi alternativa del metodo generico:

mock.Setup(x => x.Insert(It.IsAny<DataEntity>())).Returns(1) 
           .Callback<DataEntity>(de => insertedEntity = de);

Quindi puoi provare qualcosa di simile

Assert.AreEqual("test", insertedEntity.Description, "Wrong Description");

4
Probabilmente per quel caso particolare (a seconda che tu stia cercando di esprimere test contro lo stato o il comportamento), in alcuni casi potrebbe essere più pulito usare un It.Is<T>in a Mock.Verifyinvece di sporcare il test con temps. Ma +1 perché scommetto che ci sono molte persone che funzioneranno meglio da un esempio.
Ruben Bartelink

10

Esistono due tipi di Callbackin Moq. Uno accade prima che la chiamata ritorni; l'altro accade dopo il ritorno della chiamata.

var message = "";
mock.Setup(foo => foo.Execute(arg1: "ping", arg2: "pong"))
    .Callback((x, y) =>
    {
        message = "Rally on!";
        Console.WriteLine($"args before returns {x} {y}");
    })
    .Returns(message) // Rally on!
    .Callback((x, y) =>
    {
        message = "Rally over!";
        Console.WriteLine("arg after returns {x} {y}");
    });

In entrambi i callback possiamo:

  1. ispezionare gli argomenti del metodo
  2. cattura gli argomenti del metodo
  3. cambiare lo stato contestuale

2
In realtà, entrambi accadono prima che la chiamata ritorni (per quanto riguarda il chiamante). Vedi stackoverflow.com/a/28727099/67824 .
Ohad Schneider

5

Callbackè semplicemente un mezzo per eseguire qualsiasi codice personalizzato che desideri quando viene effettuata una chiamata a uno dei metodi del mock. Ecco un semplice esempio:

public interface IFoo
{
    int Bar(bool b);
}

var mock = new Mock<IFoo>();

mock.Setup(mc => mc.Bar(It.IsAny<bool>()))
    .Callback<bool>(b => Console.WriteLine("Bar called with: " + b))
    .Returns(42);

var ret = mock.Object.Bar(true);
Console.WriteLine("Result: " + ret);

// output:
// Bar called with: True
// Result: 42

Di recente mi sono imbattuto in un interessante caso d'uso per questo. Supponi di aspettarti alcune chiamate alla tua finta, ma avvengono contemporaneamente. Quindi non hai modo di conoscere l'ordine in cui verrebbero chiamati, ma vuoi sapere che le chiamate che ti aspettavi hanno avuto luogo (indipendentemente dall'ordine). Puoi fare qualcosa del genere:

var cq = new ConcurrentQueue<bool>();
mock.Setup(f => f.Bar(It.IsAny<bool>())).Callback<bool>(cq.Enqueue);
Parallel.Invoke(() => mock.Object.Bar(true), () => mock.Object.Bar(false));
Console.WriteLine("Invocations: " + String.Join(", ", cq));

// output:
// Invocations: True, False

BTW non lasciatevi confondere dalla distinzione fuorviante "prima Returns" e "dopo Returns". È solo una distinzione tecnica per stabilire se il codice personalizzato verrà eseguito dopo che Returnsè stato valutato o prima. Agli occhi del chiamante, entrambi verranno eseguiti prima che il valore venga restituito. Infatti, se il metodo viene voidrestituito non puoi nemmeno chiamare Returnse tuttavia funziona allo stesso modo. Per ulteriori informazioni, vedere https://stackoverflow.com/a/28727099/67824 .


1

Oltre alle altre buone risposte qui, l'ho usato per eseguire la logica prima di lanciare un'eccezione. Ad esempio, avevo bisogno di memorizzare tutti gli oggetti passati a un metodo per una verifica successiva e quel metodo (in alcuni casi di test) doveva generare un'eccezione. Chiamata .Throws(...)sul Mock.Setup(...)override l' Callback()azione e non lo chiama. Tuttavia, generando un'eccezione all'interno del callback, puoi ancora fare tutte le cose buone che un callback ha da offrire e comunque lanciare un'eccezione.

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.