2010-05-12 20 views
30

Apprezzo molto il comportamento di mocking di Moq Loose che restituisce valori predefiniti quando non sono impostate aspettative. È comodo e mi salva il codice, e agisce anche come misura di sicurezza: le dipendenze non verranno chiamate involontariamente durante il test dell'unità (purché siano virtuali).Quando si deride una classe con Moq, come posso chiamare CallBase solo per metodi specifici?

Tuttavia, sono confuso su come mantenere questi vantaggi quando il metodo in prova risulta virtuale.
In questo caso, io do voglio chiamare il codice reale per quell'unico metodo, pur avendo il resto della classe vagamente deriso.

Tutto quello che ho trovato nella mia ricerca è che potrei impostare mock.CallBase = true per garantire che il metodo venga chiamato. Tuttavia, ciò riguarda l'intera classe. Io non voglio farlo perché mi mette in un dilemma di tutte le altre proprietà e metodi nella classe che nascondono le dipendenze di chiamata: se CallBase è vero allora che ho a uno

  1. stub di installazione per tutti le proprietà e i metodi che nascondono le dipendenze - Anche se il mio test non ritiene necessario preoccuparsi di tali dipendenze, o
  2. Spero che non dimentichi di installare alcuno stub (e che nessuna nuova dipendenza venga aggiunta al codice in futuro) - I test delle unità di rischio colpiscono una vera dipendenza.

Quello che penso quello che voglio è qualcosa di simile:
mock.Setup(m => m.VirtualMethod()).CallBase();
in modo che quando chiamo mock.Object.VirtualMethod(), Moq chiama in dell'effettiva esecuzione ...

D: Con Moq, c'è qualche modo testare un metodo virtuale, quando ho deriso la classe per bloccare solo alcune dipendenze? Cioè Senza ricorrere a CallBase = true e dover interrompere tutte le dipendenze?


codice di esempio per illustrare
(utilizza MSTest, InternalsVisibleTo DynamicProxyGenAssembly2)

Nel seguente esempio, TestNonVirtualMethod passaggi, ma TestVirtualMethod fallisce - restituisce null.

public class Foo 
{ 
    public string NonVirtualMethod() { return GetDependencyA(); } 
    public virtual string VirtualMethod() { return GetDependencyA();} 

    internal virtual string GetDependencyA() { return "! Hit REAL Dependency A !"; } 
    // [... Possibly many other dependencies ...] 
    internal virtual string GetDependencyN() { return "! Hit REAL Dependency N !"; } 
} 

[TestClass] 
public class UnitTest1 
{ 
    [TestMethod] 
    public void TestNonVirtualMethod() 
    { 
     var mockFoo = new Mock<Foo>(); 
     mockFoo.Setup(m => m.GetDependencyA()).Returns(expectedResultString); 

     string result = mockFoo.Object.NonVirtualMethod(); 

     Assert.AreEqual(expectedResultString, result); 
    } 

    [TestMethod] 
    public void TestVirtualMethod() // Fails 
    { 
     var mockFoo = new Mock<Foo>(); 
     mockFoo.Setup(m => m.GetDependencyA()).Returns(expectedResultString); 
     // (I don't want to setup GetDependencyB ... GetDependencyN here) 

     string result = mockFoo.Object.VirtualMethod(); 

     Assert.AreEqual(expectedResultString, result); 
    } 

    string expectedResultString = "Hit mock dependency A - OK"; 
} 
+0

Basta una breve di ciò che il vostro/la mia esigenza è tutto: Hai bisogno di una versione 'CallBase' quando beffardo che invece di * * "Richiama l'implementazione della classe base se nessuna aspettativa sovrascrive il membro" **, i cosiddetti _Parial Mocks_, per favore chiama l'implementazione di base dei membri derisi! Vorrei nominare la nuova funzione come "CallBaseOnMockedMemberOnly': D –

risposta

6

Credo che la risposta di Lunivore fosse corretta al momento in cui è stata scritta.

Nelle versioni più recenti di Moq (penso dalla versione 4.1 del 2013) è possibile fare ciò che si desidera con la sintassi esatta che si propone. Cioè:

mock.Setup(m => m.VirtualMethod()).CallBase(); 

Questo imposta il finto allentato per chiamare l'implementazione di base di VirtualMethod invece di ritornare default(WhatEver), ma per questo membro (VirtualMethod) soltanto.


Come osserva utente BornToCode nei commenti, questo non funzionerà se il metodo ha tipo restituito void. Quando lo VirtualMethod non è valido, la chiamata Setup fornisce uno Moq.Language.Flow.ISetup<TMock, TResult> che eredita il metodo CallBase() da Moq.Language.Flow.IReturns<TMock, TResult>. Ma quando il metodo è vuoto, otteniamo un Moq.Language.Flow.ISetup<TMock> invece che manca del metodo desiderato CallBase().

+0

Non sto più facendo C# nel mio lavoro di giorno, quindi non nella posizione migliore per verificare, ma sarei felice di aggiornare la risposta accettata per riflettere la realtà attuale. Se qualcun altro può intervenire per confermare la nuova risposta di @ Jeppe, per favore fallo! -Grazie – Daryn

+0

@Daryn: Posso confermare che funziona, l'ho appena provato. La lettura di questa domanda si è conclusa accidentalmente aiutandomi a capire un problema non correlato (perché tutti i miei metodi non virtuali chiamano la loro implementazione di base?), Quindi ho pensato di restituire il favore e provarlo :) –

+0

Grazie, @CraigBrett. –

8

Poiché nessuno ha risposto a questa domanda per le età e penso che merita una risposta, ho intenzione di concentrarsi sulla questione più alto livello che hai chiesto: come mantenere questi benefici quando il metodo in prova sembra essere virtuale.

Risposta rapida: non è possibile farlo con Moq, o almeno non direttamente. Ma puoi farlo.

Diciamo che avete due aspetti di comportamento, in cui l'aspetto A è virtuale e l'aspetto B non lo è. Questo rispecchia praticamente quello che hai nella tua classe. B può usare altri metodi o no; tocca a voi.

Al momento, la tua classe Foo sta facendo due cose: sia A che B. Posso dire che sono responsabilità separate solo perché vuoi prendere in giro A fuori e testare B da solo.

Invece di cercare di deridere il metodo virtuale senza scherno fuori ogni altra cosa, è possibile:

  • mossa comportamento A in una classe separata
  • dipendenza iniettare la nuova classe con A in Foo attraverso Foo ' s costruttore
  • invoke quella classe di B.

Ora si può deridere a, e ancora chiamare il codice vero e proprio per B..N wi in realtà invocando il vero A. Puoi tenere A virtuale o accedervi tramite un'interfaccia e deriderlo. Ciò è in linea con il principio di responsabilità unica.

È possibile collegare in cascata costruttori con Foo - rendere il costruttore Foo() chiamare il costruttore Foo(new A()) - in modo che non hanno nemmeno bisogno di un quadro di iniezione di dipendenza per fare questo.

Spero che questo aiuti!

+0

Solo per verificare: confermi che se il metodo in prova è virtuale e usiamo Mock , dobbiamo HAVE impostare CallBase su true? – Daryn

+0

Sto suggerendo che se si sta chiamando CallBase in primo luogo, c'è probabilmente un modo diverso per ottenere lo stesso vantaggio e che probabilmente porta a una progettazione di codice che funziona nella maggior parte dei contesti. Forse hai uno di quei rari contesti che richiede assolutamente CallBase - non lo so. Puoi dirmi di più sul motivo per cui CallBase per un metodo sarebbe la soluzione più semplice al tuo problema? – Lunivore

+0

Non l'OP, ma penso che questa domanda sia ancora valida: CallBase è la soluzione più semplice quando si ha una logica di business abbastanza atomica che è estremamente complicata da testare attraverso un singolo punto di ingresso. Dividere le classi in un buon principio, ma a volte può essere proibitivo con codice legacy, o almeno un punto di partenza pericoloso. La scissione eccessiva porta anche a diarrea tipo, che rende la soluzione molto più difficile da leggere (ad esempio una mesh di 10 classi interdipendenti in cui vi è un solo punto di ingresso sensibile). –

-1

Ci è un modo per chiamare il metodo vero e una chiamata indietro quando il metodo è void, ma è davvero hacky. Devi richiamare la chiamata in modo esplicito e ingannare Moq nel chiamare quella vera.

Per esempio, data questa classe

public class MyInt 
{ 
    public bool CalledBlah { get; private set; } 

    public virtual void Blah() 
    { 
     this.CalledBlah = true; 
    } 
} 

scrivi tu prova in questo modo:

[Test] 
public void Test_MyRealBlah() 
{ 
    Mock<MyInt> m = new Mock<MyInt>(); 
    m.CallBase = true; 

    bool calledBlah = false; 
    m.When(() => !calledBlah) 
     .Setup(i => i.Blah()) 
     .Callback(() => { calledBlah = true; m.Object.Blah(); }) 
     .Verifiable(); 

    m.Object.Blah(); 

    Assert.IsTrue(m.Object.CalledBlah); 
    m.VerifyAll(); 
} 

L'aspetto chiave è che si traccia se la versione falsa è stato chiamato, e poi si imposta il mock up su non chiama il falso se è già stato chiamato.

Si può ancora fare qualcosa di simile se si prende args e in materia di valore:

public class MyInt 
{ 
    public List<int> CalledBlahArgs { get; private set; } 

    public MyInt() 
    { 
     this.CalledBlahArgs = new List<int>(); 
    } 

    public virtual void Blah(int a) 
    { 
     this.CalledBlahArgs.Add(a); 
    } 
} 

[Test] 
public void Test_UpdateQueuedGroups_testmockcallback() 
{ 
    Mock<MyInt> m = new Mock<MyInt>(); 
    m.CallBase = true; 

    List<int> fakeBlahArgs = new List<int>(); 

    m.Setup(i => i.Blah(It.Is<int>(a => !fakeBlahArgs.Contains(a)))) 
     .Callback<int>((a) => { fakeBlahArgs.Add(a); m.Object.Blah(a); }) 
     .Verifiable(); 

    m.Object.Blah(1); 

    Assert.AreEqual(1, m.Object.CalledBlahArgs.Single()); 
    m.Verify(b => b.Blah(It.Is<int>(id => 1 == id))); 
} 
+0

Interessante. Tuttavia, si imposta 'm.CallBase = true;', ma il richiedente ha esplicitamente detto che voleva una soluzione in cui non si impostava 'CallBase' su True perché ciò influisce anche su tutti gli altri membri' virtuali', e il richiedente desiderava evitarlo –

Problemi correlati