2010-01-04 13 views
5

En el siguiente código, ¿por qué mockTest.ToString() devuelve nulo?¿Por qué Moq no ejecuta el método ToString reemplazado?

EDITAR: Agregó comentarios en el código de ejemplo para mostrar cómo solucionar el 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 

Respuesta

7

Tanto la propiedad Name como el método ToString en TestClass son virtuales/anulables, lo que significa que Moq se burlará de ellos.

De manera predeterminada, Moq devuelve nulo para los miembros con tipos de devolución de tipo de referencia, a menos que explícitamente le indique que devuelva algo más. Como una cadena es un tipo de referencia, devuelve nulo.

Puede solucionarlo configurando CallBase en verdadero.

Configuración CallBase a true hará que las Moq para llamar a la implementación base si no se define explicitamente una anulación:

mock.CallBase = True 

En este caso, se instruirá al simulacro de utilizar la implementación base de ToString ya que ningún existe una instalación explícita (y así invocar la propiedad Name, que tiene tiene una configuración).

+0

Gracias. Muy bien explicado. Parece un comportamiento predeterminado extraño. –

+0

Sí, a veces también me sobresalta ... –

4

Porque no le ha indicado que devuelva nada más. Confía en el funcionamiento interno de su método ToString para devolver el valor de la propiedad del nombre, pero se está burlando del método ToString.

Creo que debe establecer la propiedad CallBase de su simulacro en true para especificar que las llamadas a métodos inesperados se ejecutan realmente en el objeto base.

+0

Pero él está haciendo un 'mock.SetupGet (Función (m como TestClass) m.Name) .Returns ("valor falso") '- ¿No debería eso hacerlo? –

+1

No, porque confía en su implementación 'ToString' para acceder a la propiedad' Name', y esto no sucederá porque el método 'ToString' ha sido reemplazado por el del simulacro. –

0

El origen del problema es realmente que usted es burlándose de lo que está probando.

Esta es la causa más común de este tipo de problemas. Los simulacros están diseñados para aislar lo que realmente está tratando de probar, en lugar de lo que intenta probarse a sí mismo.

Debe probar este método directamente y simular cualquier dependencia de que tenga, en lugar del objeto que está intentando probarse a sí mismo.

La única razón por la que deberías usar simulaciones parciales (que es lo que te permite configurar CallBase) es si la clase con la que estás trabajando es abstracta y quieres probar la funcionalidad de una clase abstracta aunque no tengas clases de implementación.

+0

@Anderson. Lo siento, no creo que entiendo tu respuesta. El código de ejemplo que proporcioné fue simplista a los fines de publicar la pregunta. El caso que me llevó a hacer la pregunta fue que tenía una clase de cronómetro de rendimiento donde el método ToString formateó el tiempo transcurrido para fines de registro. Necesito verificar el formato del resultado en función de varios períodos de tiempo. En este caso, ¿no es una simulación parcial del tiempo transcurrido y la configuración de CallBase en true es el único motivo para probar el método ToString? –

+0

En su caso probaría un método de ayuda que hizo el formato que aceptó un intervalo de tiempo como una entrada, en lugar de probar la clase de esta manera ... burlarse de esta manera es (imo) tipo de exceso. Aunque estoy de acuerdo en que este es uno de esos raros casos en los que un simulacro parcial tiene al menos un poco de sentido. No estoy de acuerdo con el enfoque ... Considero que el formateo es una función de una vista, sin embargo, entiendo que esta no es una opinión universal. –

1

La solución mencionada solo funciona siempre que no intente burlarse de lo que devuelve el método ToString(). P. ej. si utilizo un código auxiliar para la clase mencionada anteriormente para probar otra clase, necesito poder especificar qué devuelve una vez que se llame a ToString(). Por desgracia, incluso después de usar

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

Moq seguirá siendo devolver su propia ToString() (override "CastleProxies ....."). Si, por otro lado, configuro stub.CallBase = true;

tampoco usará mi configuración.

La solución que encontré es no usar ToString() alltogether, sino introducir una propiedad, p. Nombre, que mis clases ahora usarán en lugar de ToString() y que puedo configurar fácilmente.

Para preservar la funcionalidad de ToString() simplemente uso return Name; allí.

A veces es más fácil hacer las cosas un poco diferentes de lo habitual para ahorrarse muchos dolores de cabeza;)

+0

La respuesta aceptada funciona. –

3

Mi solución era crear una interfaz ficticia que define ToString, y añadir un método de extensión que lo hace más fácil de expecations configuración de 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(); 
    } 
} 

a continuación, puede utilizar el método de extensión de este 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(); 
} 
Cuestiones relacionadas