为什么Moq不运行重写的ToString方法?

时间:2010-01-04 10:12:26

标签: .net moq mocking

在下面的代码中,为什么mockTest.ToString()返回Null?

编辑:在示例代码中添加了注释,以说明如何解决问题。

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

5 个答案:

答案 0 :(得分:7)

TestClass上的Name属性和ToString方法都是虚拟/可覆盖的,这意味着Moq将模拟它们。

默认情况下,Moq为具有引用类型返回类型的成员返回null,除非您明确告诉它返回其他内容。由于字符串是引用类型,因此返回null。

您可以通过将CallBase设置为true来修复它。

如果您没有明确定义覆盖,则将CallBase设置为true将导致Moq调用基本实现:

mock.CallBase = True

在这种情况下,这将指示mock使用ToString的基本实现,因为不存在外部安装(因此调用Name属性, 具有安装程序)。

答案 1 :(得分:4)

因为你没有告诉它返回任何其他内容。您依靠ToString方法的内部工作来返回name属性的值,但ToString方法本身正在被模拟。

我认为您需要将模拟的CallBase属性设置为true,以指定在基础对象上实际执行意外的方法调用。

答案 2 :(得分:3)

我的解决方案是创建一个定义ToString的虚拟接口,并添加一个扩展方法,以便更容易在ToString上设置expec:

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();
    }
}

然后您可以像这样使用扩展方法:

[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();
}

答案 3 :(得分:1)

上述解决方案只有在您不试图模拟ToString()方法返回的内容时才有效。 例如。如果我要使用上面提到的类的存根来测试另一个类,我需要能够指定一旦调用ToString()它返回什么。不幸的是,即使使用

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

Moq仍将返回自己的ToString()覆盖(“CastleProxies .....”)。 另一方面,如果我设置 stub.CallBase = true;

它也不会使用我的设置。

我发现的解决方案是不使用ToString(),而是引入属性,例如名称,我的类现在将使用而不是ToString(),我可以轻松设置。

为了保留ToString()的功能,我只在那里使用return Name;

有时为了节省很多麻烦,做一些与平常不同的事情会更容易;)

答案 4 :(得分:0)

问题的根源在于您嘲笑您正在测试的内容

这是这些类型问题的最常见原因。模拟旨在隔离您正在尝试测试的内容,而不是您要测试自己的内容。

您应该直接测试此方法并模拟它可能具有的任何依赖项,而不是您尝试测试自身的对象。

你应该使用部分模拟的唯一原因(这是设置Call​​Base允许你做的)是你正在使用的类是抽象的,你想要测试抽象类的功能,即使你没有实现类。