模拟validation()调用

我正在进行unit testing以查看是否调用了一个方法。

[Fact] public void Can_Save_Project_Changes() { //Arrange var user = new AppUser() { UserName = "JohnDoe", Id = "1" }; Mock mockRepo = new Mock(); Mock<UserManager> userMgr = GetMockUserManager(); userMgr.Setup(x => x.FindByNameAsync(It.IsAny())).ReturnsAsync(new AppUser() { UserName = "JohnDoe", Id = "1" }); var contextUser = new ClaimsPrincipal(new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, user.UserName), new Claim(ClaimTypes.NameIdentifier, user.Id), })); Mock tempData = new Mock(); ProjectController controller = new ProjectController(mockRepo.Object, userMgr.Object) { TempData = tempData.Object, ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext() { User = contextUser } } }; Project project = new Project() { Name = "Test", UserID = "1", }; //Act Task result = controller.EditProject(project); //Assert mockRepo.Setup(m => m.SaveProject(It.IsAny(), user)); //This line still throws an error mockRepo.Verify(m => m.SaveProject(It.IsAny(), user)); Assert.IsType<Task>(result); var view = result.Result as ViewResult; Assert.Equal("ProjectCharts", view.ViewName); Assert.Equal("Project", view.Model.ToString()); } 

在调试时,我可以validation该方法是否在控制器中实际调用,

 //This controller line is touched walking through the code repository.SaveProject(project, user); //but this repo line is not touched public void SaveProject(Project project, AppUser user) 

调试实际上并不显示进入存储库方法。 确切的错误如下

模拟上的预期调用至少一次,但从未执行过:m => m.SaveProject(,JohnDoe)

没有配置设置。 执行调用:IRepository.ProjectClass IRepository.SaveProjects(ProjectClass,JohnDoe)’

当我进行实际的集成测试时, SaveProject方法在存储库中被触及并且似乎正常工作。 我也尝试在unit testing中分配每个Project属性,但得到了相同的错误结果

我要比Yoshi的评论更进一步。

Performed invocations消息告诉您Performed invocations了方法,但没有调用您正在validation的参数。 我根据消息猜测的是第一个参数有问题。

您需要为我发布测试才能更具体。

更新(添加测试后)

更改userMgr.Setup以返回“user”变量,而不是重复。 尽管我之前说过,这是你失败的原因 – 被测试的代码被赋予了重复,Moq正确地说你的方法没有被user调用,因为它已被重复调用。 因此将其更改为可解决此问题:

 userMgr.Setup(x => x.FindByNameAsync(It.IsAny())).ReturnsAsync(user); 

如果可以避免使用It.IsAny()那么这可以变得更强:如果预期作为参数的特定字符串被设置为测试设置的一部分,则改为给出值。

我怀疑两个“1”字符串需要相同才能使其工作,所以不要复制字符串声明一个局部变量并使用它而不是两个字符串。

我建议不要使用像1这样的值; 喜欢随机输入一些东西,这样它就不会巧合地通过。 我的意思是,想象一个以两个整数作为参数的方法:当调用该方法的Setup或Verify时,如果对这两个整数使用相同的值,即使你的代码错误地交换了值,测试也可以通过(将每个传递给错误的参数)。 如果在调用Setup或Verify时使用不同的值,那么只有在正确的参数中传递正确的值时它才会起作用。

mockRepo.Setup是多余的。 安装程序允许您指定类的行为方式,但在该行之后没有其他任何内容,因此其冗余并可以删除。 有些人使用安装程序和VerifyAll,但您可能想要阅读有关使用VerifyAll的讨论。

现在将validation更改回使用project而不是It.IsAny() 。 我希望它能起作用。

更新2

考虑一个平铺的屋顶。 每块瓷砖都有助于保护屋顶的一小部分,与屋顶下方的部分略微重叠。 当使用模拟时,平铺的屋顶就像是一系列unit testing。

每个’tile’代表一个测试夹具,覆盖真实代码中的一个类。 “重叠”表示类和它使用的东西之间的交互,必须使用模拟来定义,模拟使用诸如Setup和Verify(在Moq中)之类的东西进行测试。

如果这种模拟做得很糟糕,那么瓷砖之间的间隙会很大,而且你的屋顶可能会泄漏(即你的代码可能无法正常工作)。 关于如何严厉地进行模拟的两个例子:

  1. 当你真的不需要时,不要使用It.IsAny来检查赋予依赖项的参数。
  2. 与真实依赖项的行为方式相比,错误地定义了模拟的行为。

最后一个是你最大的风险; 但这与编写不良unit testing的风险没有什么不同(无论是否涉及模拟)。 如果我编写了一个unit testing来执行测试中的代码,但是没有做出任何断言,或者对无关紧要的事情做出断言,那将是一个弱测试。 使用It.IsAny就像是在说“我不关心这个价值是什么”,并且意味着你错过了断言该价值应该是什么的机会。

有些时候无法指定值,你必须使用It.IsAny ,另一种情况我会在一秒内回来也行。 否则,您应该始终尝试指定参数是什么,或者至少使用It.Is(comparison lambda) 。 另一次可以使用It.IsAny()是在validation尚未进行调用时,使用Times.Never作为Verify参数。 在这种情况下,总是使用它通常是一个好主意,因为它检查没有使用任何参数进行调用(避免您只是在给出的参数上做出错误的可能性)。

如果我写了一些unit testing,它给了我100%的代码覆盖率; 但没有测试所有可能的场景,那将是弱unit testing。 我是否有任何测试试图找到这些写得不好的测试? 不,并且不使用模拟的人也没有这样的测试。

回到平铺的屋顶类比……如果我没有嘲笑,并且必须使用真正的依赖关系测试每个部分,这就是我的屋顶看起来像什么。 我可以在屋顶底部的所有位置都有一块瓷砖。 到目前为止没问题。 对于下一组屋顶上的瓷砖,对于本来就是一块瓷砖,我需要一块三角形瓷砖,覆盖瓷砖将要去的地方,并覆盖下面的瓷砖(即使它们已被覆盖一块瓷砖)。 不过,还不错。 但是在屋顶上方有15块瓷砖,这将会让人筋疲力尽。

把它带到现实世界的场景中,假设我正在测试一个客户端代码片段,它使用两个WCF服务,其中一个是每次使用收费的第三方,其中一个受到Windows身份validation的保护,也许一个这些服务在到达数据层并与数据库交互之前在其业务层中具有复杂的逻辑,并且在那里的某处,我可能有一些缓存。 我敢说没有嘲笑就写出不错的测试可能被描述为过于复杂,如果它甚至可能(在一个人的一生中)……

除非你使用模拟,这允许你……

  1. 测试依赖于第三方代码的代码,而不调用它(确认前面提到的关于准确模拟的风险)。
  2. 模拟如果具有或没有正确权限的用户调用受保护的WCF服务会发生什么(考虑如何在没有模拟的情况下从自动化测试中执行此操作)
  3. 单独测试代码的各个部分,这在涉及复杂业务逻辑的情况下特别有用。 这会以指数方式减少需要测试的代码的路径数量,从而降低编写测试的成本,并降低测试成本。 想象一下,必须设置具有所有先决条件的数据库的复杂性,不仅适用于数据层测试,还适用于调用堆栈的所有测试。 现在当数据库发生变化时会发生什么?
  4. 通过validation模拟方法的调用次数来测试缓存。

(为了记录,测试的执行速度从未在我决定使用模拟中起任何作用。)

幸运的是,嘲弄很简单,几乎不需要任何理解水平,高于我在这里所说的。 只要您承认与完全集成测试相比,使用模拟是一种妥协,它可以节省开发和维护时间,任何产品经理都会感激。 因此,尽量保持瓷砖间的间隙小。

尝试像这样设置您的方法:

mockRepo.Setup(m => m.SaveProject(It.IsAny(),It.IsAny())

然后使用It.IsAny进行validation。

或者只是将It.IsAny用于您不希望(或不能)因某些原因正确检查的参数。 您也可以在后一种情况下创建自定义匹配器。

正如其他评论中所述。 问题很可能在于您设置模拟预期的参数。