2010-11-12 7 views
37

Setup come così:Ripristina la verifica fittizia in Moq?

public interface IFoo 
{ 
    void Fizz(); 
} 

[Test] 
public void A() 
{ 
    var foo = new Mock<IFoo>(MockBehavior.Loose); 

    foo.Object.Fizz(); 

    foo.Verify(x => x.Fizz()); 

    // stuff here 

    foo.Verify(x => x.Fizz(), Times.Never()); // currently this fails 
} 

Fondamentalmente vorrei inserire un codice a // stuff here per far passare foo.Verify(x => x.Fizz(), Times.Never()).

E perché questo costituisce probabilmente moq/unità di abuso di test, la mia giustificazione è così che io possa fare qualcosa di simile:

[Test] 
public void Justification() 
{ 
    var foo = new Mock<IFoo>(MockBehavior.Loose); 
    foo.Setup(x => x.Fizz()); 

    var objectUnderTest = new ObjectUnderTest(foo.Object); 

    objectUnderTest.DoStuffToPushIntoState1(); // this is various lines of code and setup 

    foo.Verify(x => x.Fizz()); 

    // reset the verification here 

    objectUnderTest.DoStuffToPushIntoState2(); // more lines of code 

    foo.Verify(x => x.Fizz(), Times.Never()); 
} 

Fondamentalmente, ho un oggetto stato in cui un bel po 'di lavoro (sia in termini di fare vari oggetti finti e altri fafing in giro) è necessario per spingerlo in stato1. Quindi voglio testare la transizione da State1 a State2. Invece di duplicare o astrarre il codice, preferirei semplicemente riutilizzare il test State1, inserirlo in State2 ed eseguire i miei Assert - tutto ciò che posso fare tranne le chiamate di verifica.

risposta

7

Non penso che sia possibile ripristinare un finto come questo. Invece, se si sa che Fizz dovrebbe essere chiamato una volta, quando la transizione allo stato 1, si può fare il vostro verifica in questo modo:

objectUnderTest.DoStuffToPushIntoState1(); 
foo.Verify(x => x.Fizz(), Times.Once()); // or however many times you expect it to be called 

objectUnderTest.DoStuffToPushIntoState2(); 
foo.Verify(x => x.Fizz(), Times.Once()); 

Detto questo, sarei ancora creare due test separati per questo. Come due test, è più facile vedere se la transizione nello stato 1 sta fallendo o se la transizione nello stato 2 non sta funzionando. Inoltre, se testati insieme in questo modo, se la transizione verso lo stato 1 fallisce, il metodo di test termina e la transizione nello stato 2 non viene testata.

Modifica

Come esempio di questo, ho provato il seguente codice con xUnit:

[Fact] 
public void Test() 
{ 
    var foo = new Mock<IFoo>(MockBehavior.Loose); 

    foo.Object.Fizz(); 
    foo.Verify(x => x.Fizz(), Times.Once(), "Failed After State 1"); 

    // stuff here 
    foo.Object.Fizz(); 
    foo.Verify(x => x.Fizz(), Times.Once(), "Failed after State 2"); 
} 

Questo test ha esito negativo con il messaggio, "Impossibile dopo Stato 2". Questo simula cosa accadrebbe se il tuo metodo che spinge foo nello stato 2 chiama Fizz. In tal caso, il secondo Verify avrà esito negativo.

Guardando il codice di nuovo, dal momento che si sta chiamando un metodo per verificare che non/non chiama un altro metodo sul finto, credo che è necessario impostare CallBase a true modo che la base DoStuffToPushIntoState2 è chiamato piuttosto che il mock del oltrepassare.

+0

Hai provato questo? Becauese per me il secondo 'foo.Verify (x => x.Fizz(), Times.Once());' restituisce sempre true - anche se non si chiama 'objectUnderTest.DoStuffToPushIntoState2();' – fostandy

+0

@fostandy, se chiami 'DoStuffToPushIntoState1()', e ciò chiama 'Fizz' una volta, quindi passerà' Times.Once() '. Se commentate 'DoStuffToPushIntoState2()' e nient'altro chiama 'Fizz',' Times.Once() 'continuerà a passare. Modificherò la mia risposta per aggiungere codice che ho testato. –

+0

Grazie alla deriva - ho frainteso te e tu hai ragione. – fostandy

1

Si tratta in effetti di un test di unità mentre si verificano due elementi in un test. La tua vita sarebbe molto più semplice se hai tolto l'inizializzazione ObjectUnderTest dal test e in un metodo di configurazione comune. Quindi i tuoi test diventano molto più leggibili e indipendenti l'uno dall'altro.

Più del codice di produzione, il codice di prova deve essere ottimizzato per la leggibilità e l'isolamento. Un test per un aspetto del comportamento del sistema non dovrebbe influire su altri aspetti. È davvero molto più semplice ricalcolare il codice comune in un metodo di installazione piuttosto che provare a reimpostare gli oggetti simulati.

ObjectUnderTest _objectUnderTest; 

[Setup] //Gets run before each test 
public void Setup() { 
    var foo = new Mock<IFoo>(); //moq by default creates loose mocks 
    _objectUnderTest = new ObjectUnderTest(foo.Object); 
} 
[Test] 
public void DoStuffToPushIntoState1ShouldCallFizz() { 
    _objectUnderTest.DoStuffToPushIntoState1(); // this is various lines of code and setup 

    foo.Verify(x => x.Fizz()); 
} 
[Test] 
public void DoStuffToPushIntoState2ShouldntCallFizz() { 
{ 
    objectUnderTest.DoStuffToPushIntoState2(); // more lines of code 
    foo.Verify(x => x.Fizz(), Times.Never()); 
} 
+1

Scusa, so che questo è un po 'vecchio, ma penso che questo non risolva il problema nella domanda, e vorrei sapere se c'è un modo semplice per estenderlo. In particolare, DoStuffToPushIntoState2 funzionerà solo se l'oggetto è già nello stato 1. Se l'API dell'oggetto consente solo di passare allo stato 1 mediante l'invocazione di DoStuffToPushIntoState1, e in tal modo invoca sempre Fizz, anche se hai fatto DoStuffToPushIntoState1 durante durante l'installazione, non è ancora possibile distinguere le chiamate a Fizz causate da DoStuffToPushIntoState2. – Weeble

+0

correct @Weble –

5

In caso MOQ persone stanno ascoltando, ho un caso in cui ho bisogno di resettare Verificare anche. Ho circa 200 test in una classe di test NUnit. Il SUT è abbastanza complicato da configurare (abbiamo bisogno di 6 mock e qualche altro codice di setup). Nella nostra intera suite di test abbiamo circa 7000 test e stiamo avendo problemi con mem, quindi sto cercando di eliminare i requisiti di mem. Voglio spostare tutte le chiamate finte a TestFixtureSetUp invece di SetUp. Questo ha funzionato, tranne per i pochi casi che controllano se un metodo è stato chiamato esattamente una volta.Il secondo test che ha verificato Times.Exactly (1) ora non riesce. Se potessi ripristinare la verifica, potrei riutilizzare la simulazione e usare mem molto più saggiamente.

So che potrei spostare l'installazione su un metodo separato. Per favore, non punirmi per quello. Sto solo presentando un caso valido in cui la possibilità di resettare la verifica sarebbe utile.

+0

Ho anche un altro caso valido - in cui il mock deve essere statico poiché viene inserito in una variabile statica nella classe (un logger). La soluzione che ho usato è stata quella citata altrove di incrementare una variabile di conteggio delle chiamate –

+4

@BradIrby Avevo lo stesso problema e sono incappato in una magica funzione di estensione MOQ chiamata ** Moq.MockExtensions.ResetCalls() **. Probabilmente è stato creato molto tempo dopo questo post. – stackunderflow

3

È possibile utilizzare il metodo di richiamata invece di verificare e contare le chiamate.

Questo è dimostrato sulla Moq Quick Start page, così:

// returning different values on each invocation 
var mock = new Mock<IFoo>(); 
var calls = 0; 
mock.Setup(foo => foo.GetCountThing()) 
    .Returns(() => calls) 
    .Callback(() => calls++); 
// returns 0 on first invocation, 1 on the next, and so on 
Console.WriteLine(mock.Object.GetCountThing()); 
4

Ho visto anche il Times.Exactly (1) errore di verifica di tutti i test unitari usando MoQ, con un messaggio di errore "è stato chiamato 2 volte". Lo vedo come un bug in MoQ, poiché mi aspetterei stati mock puliti in ogni esecuzione di test.

Il mio compito era assegnare una nuova istanza di simulazione e testare il target nella configurazione di test.

private Mock<IEntityMapper> entityMapperMock; 
private OverdraftReportMapper target; 

[SetUp] 
public void TestSetUp() 
{ 
    entityMapperMock = new Mock<IEntityMapper>(); 
    target = new OverdraftReportMapper(entityMapperMock.Object); 
} 
+0

Questo è esattamente come dovrebbe essere fatto. – Jan

0

approccio seguente funziona bene per me (utilizzando Moq.Sequence)

public void Justification() 
    { 
     var foo = new Mock<IFoo>(MockBehavior.Loose); 
     foo.Setup(x => x.Fizz()); 

     var objectUnderTest = new ObjectUnderTest(foo.Object); 

     objectUnderTest.DoStuffToPushIntoState1(); // this is various lines of code and setup 

     foo.Verify(x => x.Fizz()); 

     // Some cool stuff 

     using (Sequence.Create()) 
     { 
      foo.Setup(x => x.Fizz()).InSequence(Times.Never()) 
      objectUnderTest.DoStuffToPushIntoState2(); // more lines of code 
     } 
    } 

farmi sapere se ha funzionato per voi

78

credo che molto tempo dopo che questo post è stato creato hanno aggiunto il funzionalità che l'OP aveva richiesto, esiste un metodo di estensione Moq chiamato Moq.MockExtensions.ResetCalls().

Con questo metodo si può fare esattamente quello che lei ha voluto come illustrato di seguito:

[Test] 
public void Justification() 
{ 
    var foo = new Mock<IFoo>(MockBehavior.Loose); 
    foo.Setup(x => x.Fizz()); 

    var objectUnderTest = new ObjectUnderTest(foo.Object); 

    objectUnderTest.DoStuffToPushIntoState1(); // this is various lines of code and setup 

    foo.Verify(x => x.Fizz()); 

    foo.ResetCalls(); // *** Reset the verification here with this glorious method *** 

    objectUnderTest.DoStuffToPushIntoState2(); // more lines of code 

    foo.Verify(x => x.Fizz(), Times.Never()); 
} 
+0

Ignora il mio commento ora eliminato; Non avevo realizzato che questo fosse stato costruito a Moq. –

3

dipende da quale versione di Mock si utilizza, lo so per certo che possiamo fare questo

someMockObject.ResetCalls ();

Problemi correlati