使用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 "";
}
提前致谢!
答案 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建议的方法,也就是说你应该模拟依赖关系,而不是你的目标类本身。所有这一切归结为规则不要模拟您正在测试的内容。