验证方法被调用

jle*_*jle 23 .net c# moq mocking verify

使用Moq,我有一个非常奇怪的问题,如果我设置的方法是公共的,模拟上的设置似乎只能工作.我不知道这是一个Moq bug还是我错了(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);     
}
Run Code Online (Sandbox Code Playgroud)

该消息失败了:

Say_WhenPublic_CallsHello失败:Moq.MockException:调用没有在仿1次进行的:X => x.Hello()在Moq.Mock.ThrowVerifyException(IProxyCall预期,表达表达,倍)...

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

public virtual string Hello()
{
    return "";
}
Run Code Online (Sandbox Code Playgroud)

提前致谢!

Ada*_*lph 19

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

顺便说一下,你在这里做的事情在单元测试的背景下没有任何意义.单元测试不应该关心Say()调用内部Hello()方法.这是程序集内部的实现,而不是消耗代码的问题.

  • @Mark - true,尽管使用InternalVisibleTo属性可以使其他程序集可以看到内部结构 (3认同)

Mod*_*dan 11

我不太清楚它是如何在封面下工作的,为你提供一个技术答案,确切地知道为什么这是行为,但我想我可以帮助你的困惑.

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

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

我已经重写了你的例子来展示我将如何使用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);
}
Run Code Online (Sandbox Code Playgroud)

在我的例子中,我介绍了一个IHelloProvider的接口.您会注意到没有IHelloProvider的实现.这是我们通过使用模拟解决方案实现的目标的核心.

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

即使你拥有了一个全IHelloProvider,你仍然只是想测试的TestClass的行为,因此,使用混凝土HelloProvider很可能会让你的测试更容易失败,例如,如果有变化,以HelloProvider的行为,您不希望更改使用它的每个类的测试,您只想更改HelloProvider测试.

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

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

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

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

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

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

  • +1 - 要记住的一个重要概念(正如你所指出的)是你没有模拟被测试的类,你嘲笑它的依赖. (2认同)

And*_*mes 7

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

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

只有当您想要使用抽象方法的模拟实现来测试抽象类的功能时,部分模拟才有用.如果你不这样做,你可能不会从做部分模拟中看到很多好处.

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