代码之家  ›  专栏  ›  技术社区  ›  coolhand

Mock Verify()调用

  •  0
  • coolhand  · 技术社区  · 8 年前

    我正在进行单元测试,看看是否调用了一个方法。

    [Fact]
            public void Can_Save_Project_Changes()
            {
                //Arrange
                var user = new AppUser() { UserName = "JohnDoe", Id = "1" };
                Mock<IRepository> mockRepo = new Mock<IRepository>();
                Mock<UserManager<AppUser>> userMgr = GetMockUserManager();
                userMgr.Setup(x => x.FindByNameAsync(It.IsAny<string>())).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<ITempDataDictionary> tempData = new Mock<ITempDataDictionary>();
                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<IActionResult> result = controller.EditProject(project);
    
                //Assert
    
                mockRepo.Setup(m => m.SaveProject(It.IsAny<Project>(), user));
                //This line still throws an error
                mockRepo.Verify(m => m.SaveProject(It.IsAny<Project>(), user));
                Assert.IsType<Task<IActionResult>>(result);
                var view = result.Result as ViewResult;
                Assert.Equal("ProjectCharts", view.ViewName);
                Assert.Equal("Project", view.Model.ToString());
            }
    

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

    //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、 保存项目(,JohnDoe)

    未配置任何设置。 执行的调用: i假设。ProjectClass项目类 i假设。保存项目(ProjectClass,JohnDoe)'

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

    2 回复  |  直到 8 年前
        1
  •  2
  •   Richardissimo    8 年前

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

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

    你需要为我张贴测试,以便能够更具体。

    更新(添加测试后)

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

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

    如果使用 It.IsAny<string>() 可以避免:如果预期作为参数的特定字符串设置为测试设置的一部分,则改为提供值。

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

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

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

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

    更新2

    考虑平铺屋顶。每块瓷砖负责保护屋顶的一小部分,与下面的部分略微重叠。当使用mocking时,这个平铺屋顶就像一组单元测试。

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

    如果这种模拟做得不好,那么瓷砖之间的间隙将很大,屋顶可能会泄漏(即代码可能无法工作)。嘲弄如何做得不好的两个例子:

    1. 不检查提供给依赖项的参数,方法是使用 It.IsAny 当你真的不需要的时候。
    2. 与实际依赖项的行为相比,错误地定义了模拟的行为。

    最后一个是你最大的风险;但这与编写糟糕的单元测试的风险没有什么不同(无论它是否涉及模拟)。如果我编写了一个单元测试,它运行了被测试的代码,但没有做出任何断言,或者对无关紧要的东西做出了断言,那么这将是一个弱测试。使用 它伊萨尼 就像说“我不在乎这个值是什么”,意味着你错过了断言这个值的机会 应该

    有时无法指定值,您必须使用 它伊萨尼 ,还有一个我马上就回来的案例也可以。否则,您应该始终尝试准确地指定参数是什么,或者至少使用 It.Is<T>(comparison lambda) . 另一次可以使用 It.IsAny<T>() 是在您验证呼叫是否 已制作,使用 Times.Never 作为参数 Verify . 在这种情况下,最好始终使用它,因为它会检查没有使用任何参数进行的调用(避免您只是在给定的参数上出错)。

    如果我写了一些单元测试,让我的代码覆盖率达到100%;但没有测试所有可能的场景,这将是薄弱的单元测试。我有没有什么测试来找出这些写得不好的测试?不,不使用mocking的人也没有这样的测试。

    回到瓦片屋顶的类比。。。如果我没有mocking,并且必须使用真正的依赖项来测试每个部分,那么我的屋顶就是这样的。我可以用一块瓷砖来填补屋顶底边的所有碎片。目前没有问题。对于屋顶上的下一组瓷砖,对于一块瓷砖,我需要一块三角形瓷砖,覆盖该瓷砖的位置,并覆盖其下方的瓷砖(即使它们已经被瓷砖覆盖)。不过,还不错。但是再往上爬15块瓷砖,这会让人筋疲力尽。

    将其带到真实场景中,假设我正在测试一段客户端代码,该代码使用两个WCF服务,其中一个是第三方,每次使用收费,其中一个受windows身份验证保护,可能其中一个服务在到达数据层并与数据库交互之前,在其业务层中有复杂的逻辑,我可能有一些缓存。我敢说,如果有可能的话(在一个人的一生中),没有嘲弄地为此编写像样的测试可能会被描述为过于复杂。。。

    除非你使用嘲弄,这允许你。。。

    1. 测试依赖于第三方代码的代码,不要调用它(承认前面提到的准确模拟的风险)。
    2. 模拟具有或不具有正确权限的用户调用受保护的WCF服务时会发生什么情况(想想如何通过自动测试而不模拟)
    3. 单独测试代码的各个部分,这在涉及复杂业务逻辑的情况下尤其有价值。这将成倍地减少通过需要测试的代码的路径数量,降低编写测试和维护测试的成本。想象一下,必须使用所有先决条件设置数据库的复杂性,不仅是数据层测试,而且是调用堆栈中的所有测试。现在,当数据库发生更改时会发生什么情况?
    4. 通过验证mock的方法被调用了多少次来测试缓存。

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

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

        2
  •  2
  •   fbastian    8 年前

    尝试按以下方式设置方法:

    mockRepo。设置(m=>m.SaveProject(It.IsAny(),It。IsAny())

    然后验证是否使用它。我也是。

    或者直接使用它。对于因某种原因而不希望(或无法)正确检查的参数,请单击此处。在后一种情况下,还可以创建自定义匹配器。

    如其他评论所述。问题可能出在您设置的模拟参数上。