模拟验证()调用

时间:2018-03-17 04:20:11

标签: c# mocking xunit

我进行单元测试以查看方法是否被调用。

[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 =&gt; m.SaveProject(,JohnDoe)

     

未配置任何设置。   执行调用:   IRepository.ProjectClass   IRepository.SaveProjects(ProjectClass,JohnDoe)&#39;

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

2 个答案:

答案 0 :(得分:2)

尝试设置您的方法:

mockRepo.Setup(m =&gt; m.SaveProject(It.IsAny(),It.IsAny())

然后使用It.IsAny进行验证。

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

正如其他评论中所述。问题很可能出在你设定模拟预期的论点上。

答案 1 :(得分:1)

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

Performed invocations消息告诉您方法已被调用但未使用您正在验证的参数。根据消息我的猜测是第一个参数出现了问题。

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

更新(添加测试后)

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

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

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

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

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

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

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

更新2

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

每个&#39;瓷砖&#39;代表一个测试夹具,覆盖真实代码中的一个类。 &#39;重叠&#39;表示类与它使用的东西之间的交互,必须使用模拟定义,模拟使用Setup和Verify(在Moq中)进行测试。

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

  1. 当您真的不需要时,使用It.IsAny不检查为依赖项提供的参数。
  2. 与真实依赖的行为方式相比,错误地定义了模拟的行为。
  3. 最后一个是你最大的风险;但它与编写不良单元测试的风险没有什么不同(无论是否涉及嘲弄)。如果我编写了一个单元测试来运行测试中的代码,但是没有做出任何断言,或者对一些不重要的事情做出断言,那将是一个微弱的测试。使用It.IsAny就像是说&#34;我不在乎这个价值是什么&#34;,并且意味着你错过了断言应该的价值的机会>是。

    有些时候无法指定值,您必须使用It.IsAny,而另一种情况我会在一秒内回来也行。否则,您应该始终尝试指定参数是什么,或者至少使用It.Is<T>(comparison lambda)。可以使用It.IsAny<T>()的另一个时间是,在验证呼叫是否时,使用Times.Never作为{{1}的参数}}。在这种情况下,通常总是使用它是一个好主意,因为它检查没有使用任何参数进行调用(避免你只是在给出什么参数时出错)。

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

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

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

    除非你使用嘲笑,否则你可以......

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

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

相关问题