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

发布于 2024-08-17 05:01:28 字数 1457 浏览 3 评论 0原文

在下面的代码中为什么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

In the following code why does mockTest.ToString() return Null?

EDIT: Added comment into example code to show how to fix the problem.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

明媚殇 2024-08-24 05:01:28

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

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

您可以通过将 CallBase 设置为 true 来修复此问题。

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

mock.CallBase = True

在这种情况下,这将指示模拟使用 ToString 的基本实现,因为不存在显式安装程序(从而调用 Name 属性,其中确实有一个设置)。

Both the Name property and the ToString method on TestClass are virtual/overridable, which means that Moq will mock them.

By default, Moq returns null for members with reference type return types, unless you explicitly tell it to return something else. Since a string is a reference type, it returns null.

You can fix it by setting CallBase to true.

Setting CallBase to true will cause Moq to call the base implementation if you do not explictly define an override:

mock.CallBase = True

In this case, this will instruct the mock to use the base implementation of ToString since no eplicit Setup exists (and thus invoke the Name property, which does have a Setup).

深海夜未眠 2024-08-24 05:01:28

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

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

Because you haven't told it to return anything else. You are relying on the internal workings of your ToString method to return the value of the name property, but the ToString method itself is being mocked.

I think you need to set the CallBase property of your mock to true to specify that unexpected method calls are actually executed on the base object.

假扮的天使 2024-08-24 05:01:28

我的解决方案是创建一个定义 ToString 的虚拟接口,并添加一个扩展方法,以便更轻松地设置对 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();
    }
}

然后您可以使用扩展方法,如下所示:

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

My solution was to create a dummy interface that defines ToString, and to add an extension method that makes it easier to setup expecations on 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();
    }
}

You can then use the extension method like so:

[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();
}
恋你朝朝暮暮 2024-08-24 05:01:28

仅当您不尝试模拟 ToString() 方法返回的内容时,上述解决方案才有效。
例如,如果我要使用上述类的存根来测试另一个类,我需要能够指定调用 ToString() 后它返回的内容。不幸的是,即使使用

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

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

它也不会使用我的安装程序。

我发现的解决方案是不完全使用 ToString(),而是引入一个属性,例如 Name,我的类现在将使用该属性而不是 ToString(),并且我可以轻松设置。

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

有时,做一些与平常不同的事情会更容易,这样可以避免很多麻烦;)

The above mentioned solution works only as long as you are not trying to mock what the ToString() method returns.
E.g. if I would use a stub for above mentioned class to test another class, I need to be able to specify, what it returns once ToString() is called. Unfortunately even after using

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

Moq will still return its own ToString() override ("CastleProxies .....").
If on the other hand I set
stub.CallBase = true;

it will not use my Setup either.

The solution I found is to not use ToString() alltogether, but to introduce a property e.g. Name, which my classes will now use instead of ToString() and that I can setup easily.

In order to preserve the functionality of ToString() I just use return Name; in there.

Sometimes it is easier to do things a little different than usual to save yourself a lot of headaches ;)

神魇的王 2024-08-24 05:01:28

问题的根源实际上是您在嘲笑您正在测试的内容

这是此类问题的最常见原因。模拟旨在隔离您真正想要测试的内容,而不是您想要测试的内容本身。

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

您应该使用部分模拟(这是设置 CallBase 允许您执行的操作)的唯一原因是,如果您正在使用的类是抽象的,并且您想要测试抽象类的功能,即使您没有实现类。

The source of the issue is really that you are mocking what you are testing.

This is the most common cause of these types of issues. Mocks are designed to isolate what you are really trying to test, rather than what you are trying to test itself.

You should test this method directly and mock any dependencies it might have, rather than the object you are trying to test itself.

The only reason you should be using partial mocks (which is what setting CallBase allows you to do) is if the class you are working with is abstract and you want to test the functionality of an abstract class even though you have no implementation classes.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文