我不太了解它是如何在幕后工作的,无法为您提供技术答案,确切说明为什么会出现这种行为,但我想我可以帮助您解决困惑。
在您的示例中,您正在调用方法 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 对象,因为我们实际上想要测试我们编写的代码。我们创建了一个 Mock IHelloProvider,并说我们希望它调用它的 Hello() 方法,并在它调用时返回字符串“Hello World”。
然后我们调用 Say(),并像以前一样验证结果。
要意识到的重要一点是我们对 IHelloProvider 的行为不感兴趣,因此我们可以模拟它以使测试更容易。我们对 TestClass 的行为感兴趣,所以我们创建了一个实际的 TestClass 而不是 Mock,这样我们就可以测试它的实际行为。
我希望这有助于澄清正在发生的事情。