asp.net-mvc – Moq的意外验证行为
Moq让我对我的最新项目感到有点疯狂.我最近升级到版本4.0.10827,我注意到在我看来是一个新的行为.
基本上,当我在我正在测试的代码中调用我的模拟函数(在本例中为MakeCall)时,我传入了一个对象(TestClass).我正在测试的代码在调用MakeCall之前和之后对TestClass对象进行了更改.代码完成后,我会调用Moq的Verify函数.我的期望是,Moq将记录我传入MakeCall的完整对象,可能是通过深度克隆等机制.通过这种方式,我将能够验证MakeCall是否被我希望调用的确切对象调用.不幸的是,这不是我所看到的. 我试图在下面的代码中说明这一点(希望在此过程中澄清一点). >我首先创建一个新的TestClass对象.它的Var属性设置为“1”. 对我来说,似乎很清楚Moq只保留对原始TestClass对象的引用,允许我改变其值而不受惩罚,对我的测试结果产生负面影响. 关于测试代码的一些注意事项. IMyMockedInterface是我嘲笑的界面. TestClass是我传递给MakeCall方法的类,因此用于演示我遇到的问题.最后,When_Testing是包含测试代码的实际测试类.它使用Machine.Specifications框架,这就是为什么有一些奇怪的项目(‘因为’,’它应该…’).这些只是框架调用以执行测试的委托.如果需要,应该很容易删除它们并将包含的代码放入标准函数中.我把它保留为这种格式,因为它允许所有Validate调用完成(与’Arrange,Act Assert’范例相比).只是为了澄清,下面的代码不是我遇到问题的实际代码.它只是为了说明问题,因为我在多个地方看到了同样的行为. using Machine.Specifications; // Moq has a conflict with MSpec as they both have an 'It' object. using moq = Moq; public interface IMyMockedInterface { int MakeCall(TestClass obj); } public class TestClass { public string Var { get; set; } // Must override Equals so Moq treats two objects with the // same value as equal (instead of comparing references). public override bool Equals(object obj) { if ((obj != null) && (obj.GetType() != this.GetType())) return false; TestClass t = obj as TestClass; if (t.Var != this.Var) return false; return true; } public override int GetHashCode() { int hash = 41; int factor = 23; hash = (hash ^ factor) * Var.GetHashCode(); return hash; } public override string ToString() { return MvcTemplateApp.Utilities.ClassEnhancementUtilities.ObjectToString(this); } } [Subject(typeof(object))] public class When_Testing { // TestClass is set up to contain a value of 'one' protected static TestClass t = new TestClass() { Var = "one" }; protected static moq.Mock<IMyMockedInterface> mockedObject = new moq.Mock<IMyMockedInterface>(); Because of = () => { mockedObject.Object.MakeCall(t); }; // Test One // Expected: Moq should verify that MakeCall was called with a TestClass with a value of 'one'. // Actual: Moq does verify that MakeCall was called with a TestClass with a value of 'one'. // Result: This is correct. It should_verify_that_make_call_was_called_with_a_value_of_one = () => mockedObject.Verify(o => o.MakeCall(new TestClass() { Var = "one" }),moq.Times.Once()); // Update the original object to contain a new value. It should_update_the_test_class_value_to_two = () => t.Var = "two"; // Test Two // Expected: Moq should verify that MakeCall was called with a TestClass with a value of 'one'. // Actual: The Verify call fails,claiming that MakeCall was never called with a TestClass instance with a value of 'one'. // Result: This is incorrect. It should_verify_that_make_call_was_called_with_a_class_containing_a_value_of_one = () => mockedObject.Verify(o => o.MakeCall(new TestClass() { Var = "one" }),moq.Times.Once()); // Test Three // Expected: Moq should fail to verify that MakeCall was called with a TestClass with a value of 'two'. // Actual: Moq actually does verify that MakeCall was called with a TestClass with a value of 'two'. // Result: This is incorrect. It should_fail_to_verify_that_make_call_was_called_with_a_class_containing_a_value_of_two = () => mockedObject.Verify(o => o.MakeCall(new TestClass() { Var = "two" }),moq.Times.Once()); } 我有几个问题: 这是预期的行为吗? 我谦卑地感谢你提供任何帮助. 编辑: // This is the MVC Controller Action that I am testing. Note that it // makes changes to the 'searchProjects' object before and after // calling 'repository.SearchProjects'. [HttpGet] public ActionResult List(int? page,[Bind(Include = "Page,SearchType,SearchText,BeginDate,EndDate")] SearchProjects searchProjects) { int itemCount; searchProjects.ItemsPerPage = profile.ItemsPerPage; searchProjects.Projects = repository.SearchProjects(searchProjects,profile.UserKey,out itemCount); searchProjects.TotalItems = itemCount; return View(searchProjects); } // This is my test class for the controller's List action. The controller // is instantiated in an Establish delegate in the 'with_project_controller' // class,along with the SearchProjectsRequest,SearchProjectsRepositoryGet,// and SearchProjectsResultGet objects which are defined below. [Subject(typeof(ProjectController))] public class When_the_project_list_method_is_called_via_a_get_request : with_project_controller { protected static int itemCount; protected static ViewResult result; Because of = () => result = controller.List(s.Page,s.SearchProjectsRequest) as ViewResult; // This test fails,as it is expecting the 'SearchProjects' object // to contain: // Page,EndDate and ItemsPerPage It should_call_the_search_projects_repository_method = () => s.Repository.Verify(r => r.SearchProjects(s.SearchProjectsRepositoryGet,s.UserKey,out itemCount),moq.Times.Once()); // This test succeeds,EndDate,ItemsPerPage,// Projects and TotalItems It should_call_the_search_projects_repository_method = () => s.Repository.Verify(r => r.SearchProjects(s.SearchProjectsResultGet,moq.Times.Once()); It should_return_the_correct_view_name = () => result.ViewName.ShouldBeEmpty(); It should_return_the_correct_view_model = () => result.Model.ShouldEqual(s.SearchProjectsResultGet); } ///////////////////////////////////////////////////// // Here are the values of the three test objects ///////////////////////////////////////////////////// // This is the object that is returned by the client. SearchProjects SearchProjectsRequest = new SearchProjects() { SearchType = SearchTypes.ProjectName,SearchText = GetProjectRequest().Name,Page = Page }; // This is the object I am expecting the repository method to be called with. SearchProjects SearchProjectsRepositoryGet = new SearchProjects() { SearchType = SearchTypes.ProjectName,Page = Page,ItemsPerPage = ItemsPerPage }; // This is the complete object I expect to be returned to the view. SearchProjects SearchProjectsResultGet = new SearchProjects() { SearchType = SearchTypes.ProjectName,ItemsPerPage = ItemsPerPage,Projects = new List<Project>() { GetProjectRequest() },TotalItems = TotalItems }; 解决方法
最后,您的问题是模拟框架是否应该在与模拟交互时使用您所使用的参数的快照,以便它可以准确地记录系统在交互点处的状态,而不是参数可能位于的状态.验证点.
从逻辑的角度来看,我认为这是一个合理的期望.您正在执行值为Y的操作X.如果您询问模拟“我是否执行了值为Y的操作X”,则无论系统的当前状态如何,您都希望它为“是”. 总结您遇到的问题: >首先使用引用类型参数在模拟对象上调用方法. 试图回答您的具体问题: >这是预期的行为吗? 我会说不. 我不知道,但令人怀疑的是,该项目一次会有行为促进这一点,后来被修改为仅允许仅模拟每个模拟一次使用的简单场景. 我会回答这两种方式. 从技术角度来看,解决方法是使用Test Spy而不是Mock.通过使用Test Spy,您可以记录传递的值并使用您自己的策略来记住状态,例如进行深度克隆,序列化对象,或者只存储您关心的特定值以便稍后进行比较. 从测试的角度来看,我建议您遵循“Use The Front Door First”原则.我认为有一段时间进行基于状态的测试以及基于交互的测试,但您应该尽量避免将自己与实现细节结合起来,除非交互是场景的一个重要部分.在某些情况下,您感兴趣的场景主要是关于互动(“在账户之间转移资金”),但在其他情况下,您真正??关心的是获得正确的结果(“提取10美元”).对于控制器的规范,这似乎属于查询类别,而不是命令类别.只要它们是正确的,你并不真正关心它如何得到你想要的结果.因此,我建议在这种情况下使用基于状态的测试.如果另一个规范涉及对系统发出命令,那么最终可能仍然应该考虑使用前门解决方案,但是进行基于交互的测试可能是必要或重要的.只是我的想法. 您正在使用Verify()方法,它只是不支持您使用它的方案. 我不认为Moq目前正在实施以处理这种情况. 希望这可以帮助, 德里克格里尔 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- ASp.NET下拉列表和字典
- asp.net-mvc – 如何使用RequiresJs加载typescript模块(asp
- asp.net – 如何从windows azure云服务中的excel文件中读取
- asp.net-mvc – 如何处理从ASP MVC版本4.0.0.0到4.0.0.1的转
- asp.net – 使用Model-View-Presenter模式重定向页面的最正
- asp.net-mvc-4 – .net 4.5 ASP.Net web API JSONP支持
- asp.net – 在MVC5中使用java脚本代码 – 放在哪里
- asp.net-mvc – MVC下拉列表教程?
- 在ASP.NET中拒绝用户时,’CustomIdentity’上的Serializati
- asp.net-mvc-3 – 将以“.cshtml”结尾的请求路由到控制器
- asp.net-mvc – 在发送到视图之前如何修改控制器
- asp.net-mvc – 如何使用MVC 4制作提交按钮
- asp.net – Gridview在列标题中排序/向下箭头
- C# 超高速高性能写日志 代码开源
- 在ASP.Net中使用Page_Load和Page_PreRender
- NPOI导出EXCEL报_服务器无法在发送 HTTP 标头之后
- asp.net – WebMethod以JSON格式返回值
- asp.net-mvc – 将asp.net mvc beta部署到iis 6,
- asp.net-mvc – 如何在Visual Studio 2017中构建
- .net – 此程序集由比当前加载的运行时更新的运行