C# 验证方法被调用

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1980108/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 22:23:05  来源:igfitidea点击:

Verifying a method was called

c#.netmockingmoqverify

提问by jle

Using Moq, I have a very odd issue where the setup on a mock only seems to work if the method I am setting up is public. I don't know if this is a Moq bug or if I just have this wrong (newbie to Moq). Here is the test case:

使用 Moq,我有一个非常奇怪的问题,如果我设置的方法是公开的,模拟上的设置似乎才有效。我不知道这是一个 Moq 错误还是我只是有这个错误(Moq 的新手)。这是测试用例:

public class TestClass
{
    public string Say()
    {
        return Hello();
    }

    internal virtual string Hello()
    {
        return "";
    }
}

[TestMethod]
public void Say_WhenPublic_CallsHello()
{
    Mock<TestClass> mock = new Mock<TestClass>();
    mock.Setup(x => x.Hello()).Returns("Hello World");

    string result = mock.Object.Say();
    mock.Verify(x => x.Hello(), Times.Exactly(1));
    Assert.AreEqual("Hello World", result);     
}

Which fails with this message:

失败并显示此消息:

Say_WhenPublic_CallsHello failed: Moq.MockException: Invocation was not performed on the mock 1 times: x => x.Hello() at Moq.Mock.ThrowVerifyException(IProxyCall expected, Expression expression, Times times)...

Say_WhenPublic_CallsHello 失败:Moq.MockException:未在模拟上执行调用 1 次:x => x.Hello() at Moq.Mock.ThrowVerifyException(IProxyCall expected, Expression expression, Times times)...

If I make the Hello method public like this, the test passes. What is the issue here?

如果我像这样公开 Hello 方法,则测试通过。这里有什么问题?

public virtual string Hello()
{
    return "";
}

Thanks in advance!

提前致谢!

回答by Adam Ralph

The test fails when Hello()is internal because Moq cannot provide an override of the method in this case. This means that the internal implementation of Hello()will run, rather than mock's version, causing the Verify()to fail.

测试在Hello()内部时失败,因为在这种情况下,Moq 无法提供方法的覆盖。这意味着Hello()将运行的内部实现,而不是模拟的版本,导致Verify()失败。

Incidentally, what you are doing here makes no sense in the context of a unit test. A unit test should not care that Say()calls an internal Hello()method. This is implementation internal to your assembly and not a concern of consuming code.

顺便说一句,您在这里所做的事情在单元测试的上下文中毫无意义。单元测试不应该关心Say()调用内部Hello()方法。这是程序集内部的实现,而不是使用代码的问题。

回答by Modan

I don't know enough about how this works underneath the covers to give you a technical answer as to exactly why that is the behaviour, but I think I can help with your confusion.

我对这背后的工作原理知之甚少,无法为您提供有关这种行为的确切原因的技术答案,但我想我可以帮助您解决困惑。

In your example you are calling a method Say(), and it is returning the expected text. Your expectation should notenforce a particular implementation of Say() ie that it calls an internal method called Hello() to return that string. This is why it does not pass the verify, and also why the string returned is "", ie the actual implementation of Hello() has been called.

在您的示例中,您正在调用方法 Say(),它返回预期的文本。您的期望应该执行特定的实现说(的),即它()的调用名为Hello内部方法返回的字符串。这就是为什么它没有通过验证,也是为什么返回的字符串是“”,即Hello()的实际实现已经被调用。

By making the Hello method public it appears that this has enabled Moq to intercept the call to it, and use it's implementation instead. Therefore, in this case the test appears to pass. However, in this scenario you haven't really achieved anything useful, because your test says that when you call Say() the result is "Hello World" when in acutal fact the result is "".

通过公开 Hello 方法,这似乎使 Moq 能够拦截对它的调用,并改为使用它的实现。因此,在这种情况下,测试似乎通过了。但是,在这种情况下,您并没有真正实现任何有用的东西,因为您的测试表明,当您调用 Say() 时,结果是“Hello World”,而实际上结果是“”。

I have rewritten your example to show how I would expect Moq to be used (not necessarily definitive, but hopefully clear.

我已经重写了您的示例,以展示我希望如何使用 Moq(不一定是确定的,但希望清楚。

public interface IHelloProvider
{
    string Hello();
}

public class TestClass
{
    private readonly IHelloProvider _provider;

    public TestClass(IHelloProvider provider)
    {
        _provider = provider;
    }

    public string Say()
    {
        return _provider.Hello();
    }
}

[TestMethod]
public void WhenSayCallsHelloProviderAndReturnsResult()
{
    //Given
    Mock<IHelloProvider> mock = new Mock<IHelloProvider>();
    TestClass concrete = new TestClass(mock.Object);
    //Expect
    mock.Setup(x => x.Hello()).Returns("Hello World");
    //When
    string result = concrete.Say();
    //Then
    mock.Verify(x => x.Hello(), Times.Exactly(1));
    Assert.AreEqual("Hello World", result);
}

In my example, I have introduced an interface to an IHelloProvider. You will notice that there is no implementation of IHelloProvider. This is at the heart of what we are trying to achieve by using a mocking solution.

在我的示例中,我为 IHelloProvider 引入了一个接口。您会注意到没有 IHelloProvider 的实现。这是我们试图通过使用模拟解决方案实现的核心。

We are trying to test a class (TestClass), which is dependant on something external (IHelloProvider). If you are using Test Driven Development then you probably haven't written an IHelloProvider yet, but you are aware that you will need one at some point. You want to get TestClass working first though, rather than get distracted. Or perhaps IHelloProvider uses a database, or a flat file, or is difficult to configure.

我们正在尝试测试一个类(TestClass),它依赖于外部(IHelloProvider)。如果您正在使用测试驱动开发,那么您可能还没有编写 IHelloProvider,但您知道在某个时候您将需要一个。不过,您希望首先让 TestClass 工作,而不是分心。或者可能 IHelloProvider 使用数据库或平面文件,或者难以配置。

Even if you have a fully working IHelloProvider, you are still only trying to test the behaviour of TestClass, so using a concrete HelloProvider is likely to make your test more prone to failure, for instance if there is a change to the behaviour of HelloProvider, you don't want to have to change the tests of every class that uses it, you just want to change the HelloProvider tests.

即使你有一个完全工作的 IHelloProvider,你仍然只是试图测试 TestClass 的行为,所以使用具体的 HelloProvider 可能会使你的测试更容易失败,例如如果 HelloProvider 的行为发生了变化,您不想更改使用它的每个类的测试,您只想更改 HelloProvider 测试。

Getting back to the code, we now have a class TestClass, which is dependant on an interface IHelloProvider, the implementation of which is provided at construction time (this is dependency injection).

回到代码,我们现在有一个类 TestClass,它依赖于一个接口 IHelloProvider,它的实现是在构建时提供的(这是依赖注入)。

The behaviour of Say() is that it calls the method Hello() on the IHelloProvider.

Say() 的行为是它调用 IHelloProvider 上的 Hello() 方法。

If you look back at the test, we have created an actual TestClass object, since we actually want to test the code that we have written. We have created a Mock IHelloProvider, and said that we expect it to have it's Hello() method called, and when it does to return the string "Hello World".

如果你回顾一下测试,我们已经创建了一个实际的 TestClass 对象,因为我们实际上想要测试我们编写的代码。我们已经创建了一个 Mock IHelloProvider,并说我们希望它调用它的 Hello() 方法,并在它调用时返回字符串“Hello World”。

We then call Say(), and verify the results as before.

然后我们调用 Say(),并像以前一样验证结果。

The important thing to realise is that we are not interested in the behaviour of the IHelloProvider, and so we can mock it to make testing easier. We are interested in the behaviour of TestClass, so we have created an actual TestClass not a Mock, so that we can test it's actual behaviour.

要意识到的重要一点是我们对 IHelloProvider 的行为不感兴趣,因此我们可以模拟它以使测试更容易。我们对 TestClass 的行为感兴趣,因此我们创建了一个实际的 TestClass 而不是 Mock,以便我们可以测试它的实际行为。

I hope this has helped to clarify what is going on.

我希望这有助于澄清正在发生的事情。

回答by Anderson Imes

Moq doesn't do partial mocking and can only mock public virtual methods or interfaces. When you create a Mock you are creating a completely new T and removing all implementation of any public virtual methods.

Moq 不进行部分模拟,只能模拟公共虚拟方法或接口。当您创建一个 Mock 时,您正在创建一个全新的 T 并删除任何公共虚拟方法的所有实现。

My suggestion would be to concentrate on testing the public surface area of your objectsand not their internals. Your internals will get coverage. Just make sure you have a clear idea of what your target class is and don'tmock that(in most cases).

我的建议是专注于测试物体的公共表面区域,而不是它们的内部结构。您的内部人员将得到保障。只要确保你清楚地知道你的目标类是什么,不要嘲笑它(在大多数情况下)。

Partial mocking is only useful when you want to test the functionality of an abstract classwith mocked implementations of abstract methods. If you aren't doing this, you are likely not going to see much benefit from doing a partial mock.

部分模拟仅在您想使用抽象方法的模拟实现来测试抽象类的功能时才有用。如果您不这样做,您可能不会从执行部分模拟中看到太多好处

If this is notan abstract class, you will want to focus more on the approach Modan suggests, which is to say that you should mock dependencies rather than your target class itself. All of this boils down to the rule don't mock what you are testing.

如果这不是一个抽象类,您将需要更多地关注 Modan 建议的方法,也就是说您应该模拟依赖项而不是您的目标类本身。所有这一切都归结为规则不要嘲笑您正在测试的内容