2010-01-04 12 views
5

Nel codice seguente perché mockTest.ToString() restituisce Null?Perché Moq non esegue il metodo ToString sottoposto a override?

EDIT: Commento aggiunto nel codice di esempio per mostrare come risolvere il problema.

Public Sub Main() 

    Try 

     Dim test = New TestClass 

     If test.ToString <> "stackoverflow rules" Then 
      Throw New Exception("Real Failed: Actual value: <" + test.ToString + ">") 
     End If 

     Dim mock = New Moq.Mock(Of TestClass)() 
     mock.SetupGet(Function(m As TestClass) m.Name).Returns("mock value") 

     ' As per Mark's accepted answer this is the missing line of 
     ' of code to make the code work. 
     ' mock.CallBase = True 

     Dim mockTest = DirectCast(mock.Object, TestClass) 

     If mockTest.ToString() <> "mock value" Then 
      Throw New Exception("Mock Failed: Actual value: <" + mockTest.ToString + ">") 
     End If 

     Console.WriteLine("All tests passed.") 

    Catch ex As Exception 

     Console.ForegroundColor = ConsoleColor.Red 
     Console.WriteLine(ex.ToString) 
     Console.ForegroundColor = ConsoleColor.White 

    End Try 

    Console.WriteLine() 
    Console.WriteLine("Finished!") 
    Console.ReadKey() 

End Sub 

Public Class TestClass 

    Public Sub New() 
    End Sub 

    Public Overridable ReadOnly Property Name() As String 
     Get 
      Return "stackoverflow rules" 
     End Get 
    End Property 

    Public Overrides Function ToString() As String 
     Return Me.Name 
    End Function 

End Class 

risposta

7

Sia la proprietà Name che il metodo ToString su TestClass sono virtuali/sovrascrivibili, il che significa che Moq li deriderà.

Per impostazione predefinita, Moq restituisce null per i membri con tipi di ritorno del tipo di riferimento, a meno che non venga indicato esplicitamente di restituire qualcos'altro. Poiché una stringa è un tipo di riferimento, restituisce null.

È possibile risolvere il problema impostando CallBase su true.

Impostazione CallBase true causerà Moq chiamare l'implementazione di base se non esplicitamente definisce un override:

mock.CallBase = True 

In questo caso, questo istruirà il mock di utilizzare l'implementazione di base di ToString dal momento che nessun Il programma di installazione epocale esiste (e quindi richiama la proprietà Name, che fa ha un programma di installazione).

+0

Grazie. Molto ben spiegato Sembra uno strano comportamento predefinito. –

+0

Sì, a volte anche a me ... –

4

Perché non hai detto di restituire altro. Si sta facendo affidamento sul funzionamento interno del proprio metodo ToString per restituire il valore della proprietà name, ma il metodo ToString stesso viene messo in ridicolo.

penso che è necessario impostare la proprietà CallBase del vostro finto per true per specificare che le chiamate di metodo inaspettate vengono effettivamente eseguiti sul oggetto di base.

+0

Ma lui sta facendo un mock.SetupGet (Funzione '(m come TestClass) m.Nome) .Returns ("mock value") '- non dovrebbe farlo? –

+1

No, perché sta facendo affidamento sulla sua implementazione 'ToString' per accedere alla proprietà' Name', e questo non accadrà perché il metodo 'ToString' è stato sostituito con quello nella simulazione. –

0

La fonte del problema è in realtà che si è che si sdoppia cosa si sta testando.

Questa è la causa più comune di questi tipi di problemi. I mock sono progettati per isolare ciò che stai provando a testare, piuttosto che quello che stai cercando di testare da solo.

Si consiglia di testare questo metodo direttamente e deridere qualsiasi dipendenze potrebbe avere, piuttosto che l'oggetto che si sta cercando di verificare se stessa.

L'unico motivo per cui dovresti usare parziali mock (che è l'impostazione che CallBase ti permette di fare) è se la classe con cui stai lavorando è astratta e vuoi testare la funzionalità di una classe astratta anche se non hai classi di implementazione.

+0

@Anderson. Scusa, non penso di aver capito la tua risposta. Il codice di esempio che ho dato era semplicistico ai fini della pubblicazione della domanda. Il caso che mi ha fatto porre la domanda era che avevo una lezione di cronometro delle prestazioni in cui il metodo ToString formattava il tempo trascorso per la registrazione. Devo controllare la formattazione del risultato in base a varie lunghezze di tempo. In questa istanza non è una simulazione parziale del tempo trascorso e l'impostazione di CallBase su true è l'unico motivo per testare il metodo ToString? –

+0

Nel tuo caso testerei un metodo di supporto che ha fatto la formattazione che ha accettato un intervallo come input, piuttosto che testare la classe in questo modo ... beffarsi in questo modo è (imo) una sorta di eccessivo. Sono d'accordo, tuttavia, che questo è uno di quei rari casi in cui un finto finto ha almeno un po 'di senso. Non sono d'accordo un po 'con l'approccio ... Considero la formattazione come una funzione di una visione, tuttavia capisco che questa non è un'opinione universalmente tenuta. –

1

La soluzione sopra menzionata funziona solo finché non si tenta di prendere in giro il metodo restituito da ToString(). E.g. se dovessi usare uno stub per la classe sopra menzionata per testare un'altra classe, ho bisogno di essere in grado di specificare, cosa restituisce una volta chiamato ToString(). Purtroppo anche dopo aver utilizzato

stub.Setup(s => s.ToString()).Returns("fakevalue")

Moq sarà ancora tornare proprio ToString() Azionamento ("CastleProxies ....."). Se invece ho impostato stub.CallBase = true;

non userà la mia installazione di entrambi.

La soluzione che ho trovato è quella di non utilizzare ToString() tutto sommato, ma di introdurre una proprietà ad es. Nome, che le mie classi useranno ora invece di ToString() e che posso configurare facilmente.

Per preservare la funzionalità di ToString(), utilizzo solo return Name;.

volte è più facile fare le cose un po 'diverse rispetto al solito per risparmiare un sacco di mal di testa;)

+0

La risposta accettata funziona. –

3

La mia soluzione era quella di creare un'interfaccia fittizia che definisce ToString, e di aggiungere un metodo di estensione che lo rende più facile da expecations impostazione su ToString:

public static class MoqExtensions 
{ 
    public static ISetup<IToStringable, string> SetupToString<TMock>(this Mock<TMock> mock) where TMock : class 
    { 
     return mock.As<IToStringable>().Setup(m => m.ToString()); 
    } 

    //Our dummy nested interface. 
    public interface IToStringable 
    { 
     /// <summary> 
     /// ToString. 
     /// </summary> 
     /// <returns></returns> 
     string ToString(); 
    } 
} 

È quindi possibile utilizzare il metodo di estensione in questo modo:

[Test] 
public void ExpectationOnToStringIsMet() 
{ 
    var widget = new Mock<IWidget>(); 
    widget.SetupToString().Returns("My value").Verifiable(); 

    Assert.That(widget.Object.ToString(), Is.EqualTo("My value")); 

    widget.Verify(); 
} 
Problemi correlati