模拟Verify()调用 [英] Mock Verify() Invocation

查看:102
本文介绍了模拟Verify()调用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm unit testing to see if a method is called.

[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());
        }

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

While debugging, I can verify that the method is actually called in the controller,

//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)      

调试实际上并没有显示进入存储库方法的入口.确切的错误如下

Debugging doesn't actually show entrance into the repository method. The exact error is below

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

Expected invocation on the mock at least once, but was never performed: m => m.SaveProject(, JohnDoe)

未配置任何设置. 执行的调用: IRepository.ProjectClass IRepository.SaveProjects(ProjectClass,JohnDoe)'

No setups configured. Performed invocations: IRepository.ProjectClass IRepository.SaveProjects(ProjectClass, JohnDoe)'

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

When I do an actual integration test, the SaveProject method is touched in the repository and seems to work properly. I've also tried assigning every Project property within the unit test but got the same error result

推荐答案

我将比Yoshi的评论走得更远.

I'm going to go a step further than Yoshi's comment.

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

The Performed invocations message tells you the method was called but not with the parameters that you were verifying. My guess based on the messages is that there's something wrong with the first parameter.

您需要为我发布测试,以便更加具体.

You would need to post the test for me to be able to be more specific.

更新(添加测试后)

更改userMgr.Setup以返回用户"变量,而不是重复变量.尽管我刚才说了什么,但这是导致失败的原因-被测试的代码被提供了重复项,Moq正确地说您未使用user调用您的方法,因为它是使用重复项进行调用的.因此,将其更改为此可以解决问题:

Change userMgr.Setup to return your 'user' variable, not a duplicate. Despite what I said earlier, this was the cause of your failure - the code being tested was being given a duplicate, and Moq was correctly saying that your method had not been called with user because it had been called with the duplicate. So changing it to this fixes the problem:

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

如果可以避免使用It.IsAny<string>(),则可以使其变得更加强大:如果将期望作为参数的特定字符串设置为测试设置的一部分,则改为提供值.

This could be made even stronger if the use of It.IsAny<string>() can be avoided: if the specific string that is expected as a parameter is set up as part of the test setup, then give the value instead.

我怀疑两个"1"字符串都必须相同才能进行此工作,因此与其重复该字符串,不应该声明一个局部变量,而要使用它代替两个字符串.

I suspect both of the "1" strings need to be identical to make this work, so rather than duplicate the string declare a local variable and use that instead of both strings.

我建议不要使用像1这样的值;宁愿随机输入某些内容,以免它同时发生.我的意思是,假设有一个方法使用两个整数作为参数:调用该方法的Setup或Verify时,如果两个整数都使用相同的值,则即使代码错误地将值交换为(将每个参数传递给错误的参数).如果在调用Setup或Verify时使用不同的值,则只有在正确的参数中传递了正确的值后,它才起作用.

I would suggest never using values like 1; prefer to randomly type something, so that it doesn't coincidentally pass. By which I mean, imagine a method which takes two integers as parameters: when calling Setup or Verify for that method, if you use the same value for both those integers, the test could pass even if your code has mistakenly swapped the values over (passing each into the wrong parameter). If you use different values when calling Setup or Verify, then it will only work when the correct value is passed in the correct parameter.

mockRepo.Setup是多余的.安装程序使您可以指定类的行为,但是在此之后没有其他事情了,因此它是多余的,可以删除.有些人将安装程序与VerifyAll一起使用,但您可能想阅读有关使用

mockRepo.Setup is redundant. Setup allows you to specify how the class behaves but there is nothing else after that on the line, so its redundant and can be removed. Some people use setup along with VerifyAll but you might want to read this discussion about using VerifyAll.

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

Now change your verify back to using project rather than It.IsAny<Project>(). I would expect it to work.

更新2

考虑平铺的屋顶.每块瓷砖负责保护屋顶的一小部分,将其下面的部分稍稍重叠.平铺的屋顶就像使用模拟时的单元测试的集合.

Consider a tiled roof. Each tile is responsible for protecting one small part of the roof, slightly overlapping the ones below it. That tiled roof is like a collection of unit tests when using mocking.

每个平铺"代表一种测试装置,涵盖了真实代码中的一类. 重叠"表示类与其使用的事物之间的交互作用,必须使用模拟进行定义,而模拟则应使用设置和验证(在Moq中进行测试)进行模拟.

Each 'tile' represents one test fixture, covering one class in the real code. The 'overlapping' represents the interaction between the class and the things it uses, which has to be defined using mocks, which are tested using things like Setup and Verify (in Moq).

如果模拟效果不佳,则图块之间的间隙将很大,并且屋顶可能会泄漏(即,您的代码可能无法正常工作).关于如何严重地嘲笑的两个示例:

If this mocking is done badly, then the gaps between the tiles will be big, and your roof could leak (i.e. your code might not work). Two examples of how mocking can be done badly:

  1. 在确实不需要时,不使用It.IsAny来检查赋予依赖项的参数.
  2. 与真实依赖关系的行为相比,错误定义了模拟的行为.
  1. Not checking the parameters which are given to the dependencies, by using It.IsAny when you really don't need to.
  2. Incorrectly defining the behaviour of the mock compared to how the real dependency would behave.

最后一个风险是最大的风险;但这与编写不良的单元测试的风险没有什么不同(无论它是否涉及模拟).如果我编写了一个单元测试来行使被测代码,但随后又未提出任何断言,或者对无关紧要的事情做出断言,那将是一个较弱的测试.使用It.IsAny就像说我不在乎这个值是什么",这意味着您错过了断言该应该应该是什么值的机会.

That last one is your biggest risk; but it's no different than the risk of writing bad unit tests (regardless of whether it involves mocking). If I wrote a unit test which exercised the code under test but then failed to make any assertions, or made an assertion about something that doesn't matter, that would be a weak test. Using It.IsAny is like saying "I don't care what this value is", and means you're missing the opportunity to assert what that value should be.

有时无法指定该值,您必须使用It.IsAny,而我稍后再讲另一种情况也是可以的.否则,您应该始终尝试指定参数是什么,无论是准确地还是至少使用It.Is<T>(comparison lambda).另一种可以使用It.IsAny<T>()的方法是,当您使用Times.Never作为Verify的参数来验证是否进行了 呼叫时.在这种情况下,始终使用它通常是一个好主意,因为它会检查是否未使用任何参数进行调用(避免您可能只是对给出的参数犯了错误).

There are times when it's not possible to specify the value, where you have to use It.IsAny, and one other case I'll come back to in a second is also OK. Otherwise, you should always try to specify what the parameters are, either exactly, or at least using It.Is<T>(comparison lambda). The one other time it's ok to use It.IsAny<T>() is when you are verifying that a call has not been made, using Times.Never as a parameter to Verify. In this case, it is usually a good idea to always use it, since it checks the call has not been made with any parameter (avoiding the possibility that you have simply made an error on what parameters are given).

如果我编写了一些单元测试,使我获得了100%的代码覆盖率;但并未测试所有可能的方案,那将是较弱的单元测试.我是否有任何测试来尝试找到这些写得不好的测试?不,不使用嘲笑的人也没有类似的测试.

If I wrote some unit tests which gave me 100% code coverage; but didn't test all the possible scenarios, that would be weak unit testing. Do I have any tests to try to find these badly written tests? No, and people who don't use mocking don't have tests like that either.

回到屋顶瓦片的类比...如果我没有嘲笑,必须使用真实的依赖关系测试每个零件,这就是屋顶的样子.我可以在屋顶的底部边缘放置一块用于所有位的瓷砖.到目前为止没有问题.对于本来是屋顶上的下一组瓷砖,对于本来就是一个瓷砖,我需要一个三角形的瓷砖,覆盖该瓷砖将要去到的地方,并覆盖其下面的瓷砖(即使它们已经被覆盖)了.瓷砖).不过,还算不错.但是屋顶上再有15块瓷砖,这将使您筋疲力尽.

Going back to the tiled roof analogy... if I didn't have mocking, and had to test each part using the real dependencies here's what my roof would look like. I could have a tile for all of the bits at the bottom edge of the roof. No problem so far. For what would have been the next set of tiles up the roof, for what would have been one tile, I need a triangular tile, covering where that tile would have gone, and covering the tiles below it (even though they are already covered by a tile). Still, not too bad. But 15 tiles further up the roof, this is going to get exhausting.

将其带入现实世界中,假设我正在测试一段客户端代码,该代码使用两个WCF服务,其中一个是按使用收费的第三方,其中一个受Windows身份验证保护,也许其中一种服务在到达数据层并与数据库进行交互之前在其业务层中具有复杂的逻辑,而在其中的某个地方,我可能会有一些缓存.我敢说为此编写体面的测试而不进行模拟,如果可能的话(在一个人的一生中)可能被描述为过于复杂.

Bringing that to a real world scenario, imagine I'm testing a client-side piece of code, which uses two WCF services, one of which is a third party that charges per use, one of which is protected by windows authentication, maybe one of those services has complex logic in its business layer before reaching the data layer and interacting with a database, and somewhere in there, I might have some caching. I daresay writing decent tests for this without mocking could be described as overly-convoluted, if it's even possible (in one person's lifetime)...

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

Unless you use mocking, which allows you to...

  1. 在不调用第三方代码的情况下测试您依赖于第三方代码的代码(确认了前面提到的准确模拟代码的风险).
  2. 模拟如果具有或没有正确权限的用户称为受保护的WCF服务,将会发生什么情况(想想如何从自动测试中进行操作而不进行模拟)
  3. 隔离地测试代码的各个部分,这在涉及复杂的业务逻辑时特别有价值.这成倍地减少了需要测试的代码路径的数量,从而减少了编写测试以及维护测试的成本.想象一下,必须具有所有先决条件的数据库的复杂性,这不仅包括数据层测试,还包括调用堆栈中的所有测试.现在,当数据库发生更改时会发生什么?
  4. 通过验证模拟方法的调用次数来测试缓存.

(根据记录,测试的执行速度在我决定使用模拟的过程中从未发挥任何作用.)

(For the record, speed of execution of the tests has never played any part in my decision to use mocking.)

幸运的是,模拟很简单,几乎不需要我在这里阐述的任何理解水平.只要您承认与完全集成测试相比,使用模拟是一种折衷,它就可以节省开发和维护时间,这是任何产品经理都将不胜感激的.因此,请尽量减小瓷砖之间的间隙.

Luckily mocking is simple, requiring barely any level of comprehension above what I have spelled out here. As long as you acknowledge that using mocking is a compromise compared to full-on integration testing, it yields the kind of savings in development and maintenance time that any product manager will be grateful for. So try to keep the gaps between your tiles small.

这篇关于模拟Verify()调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆