Verifica di un parametro specifico con Moq


170
public void SubmitMessagesToQueue_OneMessage_SubmitSuccessfully()
{
    var messageServiceClientMock = new Mock<IMessageServiceClient>();
    var queueableMessage = CreateSingleQueueableMessage();
    var message = queueableMessage[0];
    var xml = QueueableMessageAsXml(queueableMessage);
    messageServiceClientMock.Setup(proxy => proxy.SubmitMessage(xml)).Verifiable();
    //messageServiceClientMock.Setup(proxy => proxy.SubmitMessage(It.IsAny<XmlElement>())).Verifiable();

    var serviceProxyFactoryStub = new Mock<IMessageServiceClientFactory>();
    serviceProxyFactoryStub.Setup(proxyFactory => proxyFactory.CreateProxy()).Returns(essageServiceClientMock.Object);
    var loggerStub = new Mock<ILogger>();

    var client = new MessageClient(serviceProxyFactoryStub.Object, loggerStub.Object);
    client.SubmitMessagesToQueue(new List<IMessageRequestDTO> {message});

    //messageServiceClientMock.Verify(proxy => proxy.SubmitMessage(xml), Times.Once());
    messageServiceClientMock.Verify();
}

Sto iniziando a usare Moq e lottando un po '. Sto cercando di verificare che messageServiceClient sta ricevendo il parametro giusto, che è un XmlElement, ma non riesco a trovare alcun modo per farlo funzionare. Funziona solo quando non controllo un determinato valore.

Qualche idea?

Risposta parziale: ho trovato un modo per testare che l'XML inviato al proxy è corretto, ma continuo a non pensare che sia il modo giusto di farlo.

public void SubmitMessagesToQueue_OneMessage_SubmitSuccessfully()
{
    var messageServiceClientMock = new Mock<IMessageServiceClient>();
    messageServiceClientMock.Setup(proxy => proxy.SubmitMessage(It.IsAny<XmlElement>())).Verifiable();
    var serviceProxyFactoryStub = new Mock<IMessageServiceClientFactory>();
    serviceProxyFactoryStub.Setup(proxyFactory => proxyFactory.CreateProxy()).Returns(messageServiceClientMock.Object);
    var loggerStub = new Mock<ILogger>();

    var client = new MessageClient(serviceProxyFactoryStub.Object, loggerStub.Object);
    var message = CreateMessage();
    client.SubmitMessagesToQueue(new List<IMessageRequestDTO> {message});

    messageServiceClientMock.Verify(proxy => proxy.SubmitMessage(It.Is<XmlElement>(xmlElement => XMLDeserializer<QueueableMessage>.Deserialize(xmlElement).Messages.Contains(message))), Times.Once());
}

A proposito, come posso estrarre l'espressione dalla chiamata di verifica?

Risposte:


252

Se la logica di verifica non è banale, sarà complicato scrivere un metodo lambda di grandi dimensioni (come mostra l'esempio). Potresti mettere tutte le dichiarazioni di prova in un metodo separato, ma non mi piace farlo perché interrompe il flusso di lettura del codice di prova.

Un'altra opzione è utilizzare un callback nella chiamata di installazione per memorizzare il valore che è stato passato nel metodo deriso e quindi scrivere Assertmetodi standard per convalidarlo. Per esempio:

// Arrange
MyObject saveObject;
mock.Setup(c => c.Method(It.IsAny<int>(), It.IsAny<MyObject>()))
        .Callback<int, MyObject>((i, obj) => saveObject = obj)
        .Returns("xyzzy");

// Act
// ...

// Assert
// Verify Method was called once only
mock.Verify(c => c.Method(It.IsAny<int>(), It.IsAny<MyObject>()), Times.Once());
// Assert about saveObject
Assert.That(saveObject.TheProperty, Is.EqualTo(2));

6
Un grande vantaggio di questo approccio è che ti darà specifici fallimenti di test su come l'oggetto non è corretto (mentre stai testando ciascuno individualmente).
Rob Church,

1
Pensavo di essere stato l'unico a farlo, felice di vedere che è un approccio ragionevole!
Will Appleby

3
Penso che usare It.Is <MyObject> (validatore) secondo Mayo sia migliore in quanto evita il modo leggermente imbarazzante di salvare il valore del parametro come parte del lambda
stevec

questo thread è sicuro, ad esempio quando si eseguono test in parallelo?
Anton Tolken,

@AntonTolken Non l'ho provato, ma nel mio esempio l'oggetto che viene aggiornato è una variabile locale (saveObject), quindi dovrebbe essere thread-safe.
Rich Tebb,

113

Ho verificato le chiamate nello stesso modo - credo che sia il modo giusto di farlo.

mockSomething.Verify(ms => ms.Method(
    It.IsAny<int>(), 
    It.Is<MyObject>(mo => mo.Id == 5 && mo.description == "test")
  ), Times.Once());

Se la tua espressione lambda diventa ingombrante, potresti creare una funzione che accetta MyObjectcome input e output true/ false...

mockSomething.Verify(ms => ms.Method(
    It.IsAny<int>(), 
    It.Is<MyObject>(mo => MyObjectFunc(mo))
  ), Times.Once());

private bool MyObjectFunc(MyObject myObject)
{
  return myObject.Id == 5 && myObject.description == "test";
}

Inoltre, tieni presente un bug con Mock in cui il messaggio di errore indica che il metodo è stato chiamato più volte quando non è stato chiamato affatto. Ormai potrebbero averlo corretto, ma se vedi quel messaggio potresti considerare di verificare che il metodo sia stato effettivamente chiamato.

EDIT: Ecco un esempio di chiamata verifica più volte per quegli scenari in cui si desidera verificare che si chiama una funzione per ciascun oggetto in un elenco (ad esempio).

foreach (var item in myList)
  mockRepository.Verify(mr => mr.Update(
    It.Is<MyObject>(i => i.Id == item.Id && i.LastUpdated == item.LastUpdated),
    Times.Once());

Stesso approccio per l'installazione ...

foreach (var item in myList) {
  var stuff = ... // some result specific to the item
  this.mockRepository
    .Setup(mr => mr.GetStuff(item.itemId))
    .Returns(stuff);
}

Quindi ogni volta che GetStuff viene chiamato per quell'elemento ID, restituirà elementi specifici per quell'elemento. In alternativa, è possibile utilizzare una funzione che accetta itemId come input e restituisce elementi.

this.mockRepository
    .Setup(mr => mr.GetStuff(It.IsAny<int>()))
    .Returns((int id) => SomeFunctionThatReturnsStuff(id));

Un altro metodo che ho visto su un blog qualche tempo fa (Phil Haack forse?) Prevedeva che la configurazione tornasse da una sorta di oggetto dequeue - ogni volta che veniva chiamata la funzione si estraeva un oggetto da una coda.


1
Grazie, ha senso per me. Quello che ancora non riesco a capire è quando specificare i dettagli in Setup o Verifica. È abbastanza confuso. Al momento, sto solo consentendo qualsiasi cosa in Setup e specificando i valori in Verifica.
Luis Mirabal,

Come pensi che posso controllare i messaggi quando ci sono più chiamate? Il client accetta i messaggi e può creare più messaggi queueable, che finiranno in più chiamate e in ognuna di quelle chiamate, devo controllare diversi messaggi. Sto ancora lottando con i test unitari in generale, non ne ho ancora molta familiarità.
Luis Mirabal,

Non penso che ci sia un magico proiettile d'argento in termini di come dovresti farlo. Ci vuole pratica e inizi a migliorare. Per quanto mi riguarda, specifico i parametri solo quando ho qualcosa con cui confrontarli e quando non sto già testando quel parametro in un altro test. Per quanto riguarda le chiamate multiple ci sono diversi approcci. Per impostare e verificare una funzione chiamata più volte, di solito chiamo setup o verifico (Times.Once ()) per ogni chiamata che mi aspetto, spesso con un ciclo for. È possibile utilizzare i parametri specifici per isolare ogni chiamata.
Mayo,

Ho aggiunto alcuni esempi per più chiamate - vedi risposta sopra.
Mayo,

1
"Inoltre, sii consapevole di un bug con Mock in cui il messaggio di errore indica che il metodo è stato chiamato più volte quando non è stato chiamato affatto. Potrebbero averlo risolto ormai - ma se vedi quel messaggio potresti considerare di verificare che il metodo è stato effettivamente chiamato ". - Un bug come questo invalida completamente una libreria beffarda IMHO. Com'era ironico che non avevano un codice di prova adeguato per questo :-)
Gianluca Ghettini,

20

Un modo più semplice sarebbe quello di fare:

ObjectA.Verify(
    a => a.Execute(
        It.Is<Params>(p => p.Id == 7)
    )
);

Non riesco a farlo funzionare, sto cercando di verificare che il mio metodo sia stato chiamato con Code.WRCC come parametro. Ma il mio test passa sempre, anche se il parametro passato è WRDD .. m.Setup(x => x.CreateSR(Code.WRDD)).ReturnsAsync(0); await ClassUnderTest.CompleteCC(accountKey, It.IsAny<int>(), mockRequest); m.Verify(x => x.CreateSR(It.Is<Code>(p => p == Code.WRCC)), Times.Once());
Greg Quinn,

1

Credo che il problema sia il fatto che Moq verificherà l'uguaglianza. E, dal momento che XmlElement non sostituisce Equals, la sua implementazione verificherà l'uguaglianza di riferimento.

Non puoi usare un oggetto personalizzato, quindi puoi sovrascrivere gli uguali?


Sì, ho finito per farlo. Mi sono reso conto che il problema stava controllando l'Xml. Nella seconda parte della domanda ho aggiunto una possibile risposta deserializzando l'xml a un oggetto
Luis Mirabal,

1

Aveva anche uno di questi, ma il parametro dell'azione era un'interfaccia senza proprietà pubbliche. Ho finito per usare It.Is () con un metodo separato e all'interno di questo metodo ho dovuto fare qualche beffa dell'interfaccia

public interface IQuery
{
    IQuery SetSomeFields(string info);
}

void DoSomeQuerying(Action<IQuery> queryThing);

mockedObject.Setup(m => m.DoSomeQuerying(It.Is<Action<IQuery>>(q => MyCheckingMethod(q)));

private bool MyCheckingMethod(Action<IQuery> queryAction)
{
    var mockQuery = new Mock<IQuery>();
    mockQuery.Setup(m => m.SetSomeFields(It.Is<string>(s => s.MeetsSomeCondition())
    queryAction.Invoke(mockQuery.Object);
    mockQuery.Verify(m => m.SetSomeFields(It.Is<string>(s => s.MeetsSomeCondition(), Times.Once)
    return true
}
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.