如何在ASP.NET MVC中模拟模型?

med*_*4th 3 asp.net-mvc unit-testing mocking

我做了一个自定义模型,我想嘲笑它.我对MVC很新,对单元测试也很陌生.我见过的大多数方法都为类创建了一个接口,然后创建一个实现相同接口的模拟器.但是,当实际将接口传递到View时,我似乎无法使其工作.提示"简化"的例子:

模型-

public interface IContact
{
    void SendEmail(NameValueCollection httpRequestVars);
}

public abstract class Contact : IContact
{
    //some shared properties...
    public string Name { get; set; }

    public void SendEmail(NameValueCollection httpRequestVars = null)
    {
        //construct email...
    }
}

public class Enquiry : Contact
{
    //some extra properties...
}
Run Code Online (Sandbox Code Playgroud)

视图-

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<project.Models.IContact>" %>

<!-- other html... -->

<td><%= Html.TextBoxFor(model => ((Enquiry)model).Name)%></td>
Run Code Online (Sandbox Code Playgroud)

控制器 -

    [HttpPost]
    public ActionResult Index(IContact enquiry)
    {
        if (!ModelState.IsValid)
            return View(enquiry);

        enquiry.SendEmail(Request.ServerVariables);
        return View("Sent", enquiry);
    }
Run Code Online (Sandbox Code Playgroud)

单元测试 -

    [Test]
    public void Index_HttpPostInvalidModel_ReturnsDefaultView()
    {
        Enquiry enquiry = new Enquiry();
        _controller.ModelState.AddModelError("", "dummy value");

        ViewResult result = (ViewResult)_controller.Index(enquiry);

        Assert.IsNullOrEmpty(result.ViewName);
    }

    [Test]
    public void Index_HttpPostValidModel_CallsSendEmail()
    {
        MockContact mock = new MockContact();

        ViewResult result = (ViewResult)_controller.Index(mock);

        Assert.IsTrue(mock.EmailSent);
    }

public class MockContact : IContact
{
    public bool EmailSent = false;

    void SendEmail(NameValueCollection httpRequestVars)
    {
        EmailSent = true;
    }
}
Run Code Online (Sandbox Code Playgroud)

在HttpPost上,我得到一个"无法创建接口的实例"异常.我似乎无法拥有我的蛋糕(通过模型)并吃掉它(通过模拟进行单元测试).也许有更好的单元测试模型绑定到视图?

谢谢,

地中海

Chr*_*sic 9

我要把它扔出去,如果你需要嘲笑你的模型你做错了.你的模特应该是愚蠢的财产袋.

您的模型绝对没有理由应该使用SendEmail方法.这是应该从调用EmailService的控制器调用的功能.

回答你的问题:

经过多年与分离关注(SOC)模式(如MVC,MVP,MVVM)的合作以及看到比我更聪明的人的文章(我希望我能找到一个我正在考虑这个但也许我在杂志上阅读的文章) .您最终将在企业应用程序中得出结论,最终将得到3组不同的模型对象.

以前,我是使用一组业务实体进行域驱动设计(DDD)的忠实粉丝,这些业务实体既是普通的旧c#对象(PO​​CO)又是持久无知(PI).拥有POCO/PI的域模型会为您留下一块干净的对象,其中没有与访问对象存储相关的代码,或者只有代码的1个区域具有其他具有逻辑意义的属性.

虽然这有效,并且可以在一段时间内运行良好,但最终会出现一个转折点,即表达视图,域模型和物理存储模型之间关系的复杂性变得过于复杂,无法正确表达1组实体.

要解决View,Domain和Storage的阻抗不匹配问题,您需要3套模型.您的ViewModel将与您的视图绑定完全匹配,以便于使用UI.因此,这将经常包含诸如添加List以使用对编辑视图/操作有效的值填充下拉列表的内容.

中间是域实体,这些是您应根据业务规则进行验证的实体.因此,您将在视图的两侧与/或从存储层映射到/从它们映射.在这些实体中,您可以附加代码以进行验证.我个人不喜欢使用属性并将验证逻辑耦合到您的域实体中.将验证属性耦合到ViewModel中以利用内置的MVC客户端验证功能确实很有意义.

对于验证,我建议使用像FluentValidation(或您自己的自定义库,它们并不难写)的库,它允许您将业务规则与对象分开.虽然使用MVC3的新功能可以进行远程验证,并且可以显示客户端,但这是一个处理真正业务验证的选项.

最后你有你的存储模型.正如我之前所说,我非常热衷于让PI对象能够在所有层中重复使用,因此根据您设置持久存储的方式,您可以直接使用域对象.但是如果您利用Linq2Sql,EntityFramework(EF)等工具,您很可能会使用自动生成的模型,其中包含与数据提供程序交互的代码,因此您需要将域对象映射到持久性对象.

因此,将所有这些包装起来,这将是MVC操作中的标准逻辑流程

用户去编辑产品页面

  1. EF查询数据库以获取现有产品信息,在存储库层内,EF数据对象被映射到业务实体(BE),因此所有数据层方法都返回BE并且没有与EF数据对象的外部耦合.(因此,如果您更改了数据提供程序,则除了内部实现之外,您不必更改任何代码行)

  2. 控制器获取产品BE并将其映射到产品ViewModel(VM),并为可以为下拉列表设置的不同选项添加集合

  3. 返回视图(theview,ProductVM)

用户编辑产品并提交表单

  1. 传递客户端验证(对于日期验证/数字验证很有用,而不必提交表单以供反馈)

  2. 此时,ProductVM将映射回ProductBE,您将验证业务规则ValidationFactory.Validate(ProductBE),如果它无效,则返回消息返回查看并取消编辑,否则继续

  3. 您将ProductBE传递到存储库模型,在将ProductBE映射到EF的产品数据实体的数据层的内部实现中,并更新数据库.

2016编辑:删除Interface了关注和界面分离完全正交的用法.