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()
方法.这是程序集内部的实现,而不是消耗代码的问题.
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,以便我们可以测试它的实际行为.
我希望这有助于澄清正在发生的事情.
Moq不进行部分模拟,只能模拟公共虚拟方法或接口.当您创建Mock时,您将创建一个全新的T并删除所有公共虚拟方法的实现.
我的建议是专注于测试物体的公共表面区域而不是它们的内部.你的内部人员将得到报道.只要确保你清楚地知道你的目标类是什么,并且不要嘲笑它(在大多数情况下).
只有当您想要使用抽象方法的模拟实现来测试抽象类的功能时,部分模拟才有用.如果你不这样做,你可能不会从做部分模拟中看到很多好处.
如果这不是一个抽象类,你将需要更多地关注Modan建议的方法,也就是说你应该模拟依赖关系,而不是你的目标类本身.所有这些归结为规则不要嘲笑你正在测试的东西.