2011-02-10 13 views
100
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 utilizzare Moq e sto faticando un po '. Sto cercando di verificare che messageServiceClient stia ricevendo il parametro giusto, che è un XmlElement, ma non riesco a trovare alcun modo per farlo funzionare. Funziona solo quando non controllo un particolare valore.Verifica di un parametro specifico con Moq

Qualche idea?

Risposta parziale: Ho trovato un modo per verificare che l'xml inviato al proxy sia corretto, ma continuo a non pensare che sia il modo giusto per 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 è possibile estrarre l'espressione dalla chiamata di verifica?

risposta

149

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

Un'altra opzione consiste nell'utilizzare un callback nella chiamata di installazione per memorizzare il valore passato nel metodo di simulazione e quindi scrivere i metodi standard Assert 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)); 
+5

Un grande vantaggio di questo approccio è che ti darà errori di test specifici su come l'oggetto non è corretto (come stai testando singolarmente). –

+1

Pensavo di essere l'unico a farlo, felice di vedere che è un approccio ragionevole! –

+0

Penso che l'utilizzo di It.Is (validatore) come per Mayo sia migliore in quanto evita il modo leggermente imbarazzante di salvare il valore del parametro come parte del lambda – stevec

48

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

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

Se la vostra espressione lambda diventa ingombrante, è possibile creare una funzione che prende MyObject come input e restituisce vero/falso ...

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, essere a conoscenza di un bug con Mock dove il messaggio di errore indica che il metodo è stato chiamato più volte quando non è stato chiamato affatto. Potrebbero averlo risolto, ma se vedi quel messaggio potresti considerare di verificare che il metodo sia stato effettivamente chiamato.

MODIFICA: questo è un esempio di chiamata verifica più volte per quegli scenari in cui si desidera verificare che si chiami 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()); 

Lo stesso approccio per la messa a punto ...

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

Così ogni volta che viene chiamato per GetStuff che itemId, ritornerà cose specifiche a tale 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) aveva messa a punto di ritorno da qualche tipo di oggetto dequeue - ogni volta che la funzione è stata chiamata sarebbe estrarre un elemento da una coda.

+0

Grazie, ha senso per me. Quello che ancora non riesco a capire è quando specificare i dettagli in Impostazione o Verifica. È abbastanza confuso. Al momento, sto solo consentendo qualcosa nel programma di installazione e specificando i valori in Verifica. –

+0

Come pensi di poter controllare i messaggi quando ci sono più chiamate? Il client accetta i messaggi e può creare più messaggi accodati, che terminano in più chiamate e in ciascuna di tali chiamate, devo controllare diversi messaggi. Sto ancora lottando con i test unitari in generale, non ho ancora molta familiarità con esso. –

+0

Non penso che ci sia un punto magico d'argento in termini di come dovresti farlo. Ci vuole pratica e tu inizi a migliorare. Per me, ho solo specificato i parametri quando ho qualcosa da confrontare e quando non sto già testando quel parametro in un altro test. Per quanto riguarda le chiamate multiple ci sono diversi approcci. Per l'impostazione e la verifica di una funzione chiamata più volte, di solito richiamo l'installazione o verifica (Times.Once()) per ogni chiamata che mi aspetto, spesso con un ciclo for. È possibile utilizzare i parametri specifici per isolare ogni chiamata. – Mayo

1

Credo che il problema nel fatto che Moq verificherà l'uguaglianza. E poiché XmlElement non sovrascrive Equals, l'implementazione controllerà l'uguaglianza di riferimento.

Non è possibile utilizzare un oggetto personalizzato, quindi è possibile eseguire l'override degli uguali?

+0

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

8

Un modo più semplice sarebbe quella di fare:

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