我在最近的项目上把我推向了一步。 我最近升级为4.0.10827版,我对我看来是一种新行为表示怀疑。
Basically, when I call my mocked function (MakeCall
, in this example) in the code I am testing, I am passing in an object (TestClass
). The code I am testing makes changes to the TestClass
object before and after the call to MakeCall
. Once the code has completed, I then call Moq s Verify
function. My expectation is that Moq will have recorded the complete object that I passed into MakeCall
, perhaps via a mechanism like deep cloning. This way, I will be able to verify that MakeCall
was called with the exact object I am expecting it to be called with. Unfortunately, this is not what I m seeing.
我试图在下文的法典中说明这一点(令人信服地澄清,这是这一进程中的一个轨道)。
- I first create a new
TestClass
object. ItsVar
property is set to"one"
. - I then create the mocked object,
mockedObject
, which is my test subject. - I then call the
MakeCall
method ofmockedObject
(by the way, the Machine.Specifications framework used in the example allows the code in theWhen_Testing
class to be read from top to bottom). - I then test the mocked object to ensure that it was indeed called with a
TestClass
with aVar
value of"one"
. This succeeds, as I expected it to. - I then make a change to the original
TestClass
object by re-assigning theVar
property to"two"
. - I then proceed to attempt to verify if Moq still thinks that
MakeCall
was called with aTestClass
with a value of"one"
. This fails, although I am expecting it to be true. - Finally, I test to see if Moq thinks
MakeCall
was in fact called by aTestClass
object with a value of"two"
. This succeeds, although I would initially have expected it to fail.
在我看来,似乎很显然,莫克只是在提及原始的<编码>试验目录<<>标的时,才允许我改变其价值而不受惩罚,对我的测试结果造成不利影响。
关于测试法的一些说明。 <代码>IMyMocked Interface是我模拟的接口。 http://www.un.org/Depts/DGACM/index_chinese.htm 最后,When_Releaseing
是包含测试代码的实际测试类别。 它正在使用。 因此,框架有几个奇题(因为......,应当......)。 这些代表只是框架要求进行测试的代表。 如果有此需要,应易于消除这些障碍,并将其列入标准功能。 我以这种格式离开,是因为它允许所有<代码>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());
}
我就此提出了几个问题:
Is this expected behavior?
Is this new behavior?
Is there a workaround that I am unaware of?
Am I using Verify incorrectly?
Is there a better way of using Moq to avoid this situation?
I thank you humbly for any assistance you can provide.
Edit:
Here is one of the actual tests and SUT code that I experienced this problem with. Hopefully it will act as clarification.
// 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
};