验证方法被调用

时间:2009-12-30 13:04:42

标签: c# .net mocking moq 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);     
}

此消息失败:

  

Say_WhenPublic_CallsHello失败:Moq.MockException:   模拟1次未执行调用:x =&gt; x.Hello()   在Moq.Mock.ThrowVerifyException(预期IProxyCall,表达式,时间)......

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

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

提前致谢!

3 个答案:

答案 0 :(得分:19)

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

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

答案 1 :(得分:11)

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

在您的示例中,您正在调用方法Say(),并返回预期的文本。您的期望强制执行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);
}

在我的例子中,我介绍了一个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,以便我们可以测试它的实际行为。

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

答案 2 :(得分:7)

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

我的建议是专注于测试对象的公共表面区域,而不是他们的内部。你的内部人员将得到报道。只需确保明确了解目标类是什么,模仿(在大多数情况下)。

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

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