加入收藏 | 设为首页 | 会员中心 | 我要投稿 安卓应用网 (https://www.0791zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > asp.Net > 正文

asp.net-mvc – Moq的意外验证行为

发布时间:2020-05-24 15:44:40 所属栏目:asp.Net 来源:互联网
导读:Moq让我对我的最新项目感到有点疯狂.我最近升级到版本4.0.10827,我注意到在我看来是一个新的行为. 基本上,当我在我正在测试的代码中调用我的模拟函数(在本例中为MakeCall)时,我传入了一个对象(TestClass).我正在测试的代码在调用MakeCall之前和之后对TestClas

Moq让我对我的最新项目感到有点疯狂.我最近升级到版本4.0.10827,我注意到在我看来是一个新的行为.

基本上,当我在我正在测试的代码中调用我的模拟函数(在本例中为MakeCall)时,我传入了一个对象(TestClass).我正在测试的代码在调用MakeCall之前和之后对TestClass对象进行了更改.代码完成后,我会调用Moq的Verify函数.我的期望是,Moq将记录我传入MakeCall的完整对象,可能是通过深度克隆等机制.通过这种方式,我将能够验证MakeCall是否被我希望调用的确切对象调用.不幸的是,这不是我所看到的.

我试图在下面的代码中说明这一点(希望在此过程中澄清一点).

>我首先创建一个新的TestClass对象.它的Var属性设置为“1”.
>然后我创建了模拟对象mockedObject,这是我的测试主题.
>然后我调用mockedObject的MakeCall方法(顺便说一下,示例中使用的Machine.Specifications框架允许从上到下读取When_Testing类中的代码).
>然后我测试模拟对象以确保它确实使用VarC值为“1”的TestClass调用.正如我所预料的那样,这成功了.
>然后我通过将Var属性重新分配给“two”来更改原始TestClass对象.
>然后我继续尝试验证Moq是否仍然认为使用值为“1”的TestClass调用了MakeCall.这失败了,虽然我期待它是真的.
>最后,我测试看看Moq是否认为MakeCall实际上是由一个值为“2”的TestClass对象调用的.这成功了,虽然我最初预计它会失败.

对我来说,似乎很清楚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());
}

我有几个问题:

这是预期的行为吗?
这是新的行为吗?
有没有我不知道的解决方法?
我错误地使用了验证吗?
有没有更好的方法使用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,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”,则无论系统的当前状态如何,您都希望它为“是”.

总结您遇到的问题:

(编辑:安卓应用网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读