【发布时间】:2011-04-01 22:41:37
【问题描述】:
起订量让我对我的最新项目有点抓狂。我最近升级到版本 4.0.10827,我注意到在我看来这是一种新行为。
基本上,当我在我正在测试的代码中调用我的模拟函数(在此示例中为MakeCall)时,我传入了一个对象(TestClass)。我正在测试的代码在调用MakeCall 之前和之后对TestClass 对象进行了更改。代码完成后,我将调用 Moq 的 Verify 函数。我的期望是 Moq 将记录我传递给 MakeCall 的完整对象,可能是通过像深度克隆这样的机制。这样,我将能够验证 MakeCall 是使用我期望调用它的确切对象调用的。不幸的是,这不是我所看到的。
我试图在下面的代码中说明这一点(希望在此过程中稍微澄清一下)。
- 我首先创建一个新的
TestClass对象。其Var属性设置为"one"。 - 然后我创建模拟对象
mockedObject,这是我的测试对象。 - 然后我调用
mockedObject的MakeCall方法(顺便说一下,示例中使用的Machine.Specifications 框架允许从上到下读取When_Testing类中的代码)。 - 然后我测试模拟对象以确保它确实是用
TestClass调用的,Var值为"one"。正如我所料,这成功了。 - 然后我通过将
Var属性重新分配给"two"来更改原始TestClass对象。 - 然后我继续尝试验证 Moq 是否仍然认为
MakeCall是用TestClass调用的,其值为"one"。这失败了,尽管我希望它是真的。 - 最后,我测试看看 Moq 是否认为
MakeCall实际上是由值为"two"的TestClass对象调用的。这成功了,尽管我最初预计它会失败。
对我来说似乎很清楚 Moq 仅保留对原始 TestClass 对象的引用,允许我随意更改其值,从而对我的测试结果产生不利影响。
关于测试代码的几点说明。 IMyMockedInterface 是我正在嘲笑的界面。 TestClass 是我传递给 MakeCall 方法的类,因此用于演示我遇到的问题。最后,When_Testing 是包含测试代码的实际测试类。它使用Machine.Specifications 框架,这就是为什么有一些奇怪的项目('因为','它应该......')。这些只是由框架调用以执行测试的委托。如果需要,它们应该很容易被删除,并将包含的代码放入标准函数中。我将其保留为这种格式是因为它允许完成所有Validate 调用(与“安排,执行断言”范例相比)。只是为了澄清,下面的代码不是我遇到问题的实际代码。它只是为了说明问题,因为我在多个地方都看到了同样的行为。
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());
}
对此我有几个问题:
这是预期的行为吗?
这是新行为吗?
有没有我不知道的解决方法?
我是否错误地使用了验证?
有没有更好的方法来使用 Moq 来避免这种情况?
衷心感谢您提供的任何帮助。
编辑:
这是我遇到此问题的实际测试和 SUT 代码之一。希望它能起到澄清作用。
// 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, SearchType, SearchText, BeginDate, 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, as it is expecting the 'SearchProjects' object
// to contain:
// Page, SearchType, SearchText, BeginDate, EndDate, ItemsPerPage,
// Projects and TotalItems
It should_call_the_search_projects_repository_method = () =>
s.Repository.Verify(r => r.SearchProjects(s.SearchProjectsResultGet,
s.UserKey, out itemCount), 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,
SearchText = GetProjectRequest().Name,
Page = Page,
ItemsPerPage = ItemsPerPage
};
// This is the complete object I expect to be returned to the view.
SearchProjects SearchProjectsResultGet = new SearchProjects()
{
SearchType = SearchTypes.ProjectName,
SearchText = GetProjectRequest().Name,
Page = Page,
ItemsPerPage = ItemsPerPage,
Projects = new List<Project>() { GetProjectRequest() },
TotalItems = TotalItems
};
【问题讨论】:
标签: asp.net-mvc moq mspec