2010-11-11 85 views
3

我做了一个自定义模型,我想嘲笑它。我对MVC相当陌生,对于单元测试也很新颖。我见过的大多数方法都为这个类创建了一个接口,然后做了一个实现相同接口的模拟器。但是,当我真正将接口传递给View时,我似乎无法得到这个工作。线索 “简化” 例如:如何在ASP.NET MVC中模拟模型?

型号 -

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... 
} 

查看 -

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

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

<td><%= Html.TextBoxFor(model => ((Enquiry)model).Name)%></td> 

控制器 -

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

     enquiry.SendEmail(Request.ServerVariables); 
     return View("Sent", enquiry); 
    } 

单位测试 -

[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; 
    } 
} 

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

感谢,

医学

+2

btw,一个“model”通常不包含逻辑(如SendEmail()) – Will 2010-11-11 17:28:57

+0

让我们不要打开skinny/fat控制器/模型的蠕虫病毒!你认为SendEmail()应该去哪里? – med4th 2010-11-11 17:59:16

回答

9

我打算把它扔在那里,如果你需要模拟你的模型就错了。你的模特应该是哑巴财物袋。

绝对没有理由你的模型应该有一个SendEmail方法。这是应该从调用EmailService的控制器调用的功能。

在回答你的问题:

经过多年与像MVC,MVP,MVVM关注(SOC)模式的分离工作,并从人们看到的文章比我更聪明的(我希望我能找到一个我我正在想这个,但也许我在杂志上看过)。您最终会在一个企业应用程序中得出结论,您将最终得到3组不同的模型对象。

以前,我是使用一套既简单的旧c#对象(PO​​CO)和持久性无知(PI)的业务实体来进行域驱动设计(DDD)的一个很大的爱好者。拥有POCO/PI的领域模型为您提供了一个清晰的对象,其中没有与访问对象存储相关的代码,或者具有仅用于代码的一个区域的示意性含义的其他属性。

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

要解决视图,域和存储的阻抗不匹配问题,您确实需要3组模型。您的ViewModel将完全匹配您的视图绑定,以方便它与UI的协作。因此,这通常会有一些内容,比如添加一个List来填充对您的编辑视图/操作有效的值的下拉菜单。

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

对于验证,我建议使用像FluentValidation这样的库(或者您自己定制的库,它们不难写),可以让您将业务规则与对象分开。尽管使用MVC3的新特性,您可以执行远程验证,并且可以显示客户端,但这是处理真实业务验证的一个选项。

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

所以换了这一切了,这将是在MVC行动标准逻辑流程

用户进入编辑产品页面

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

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

  3. 返回查看(theview,ProductVM)

用户编辑的产品并提交表单

  1. 客户端验证传递(日期验证/数字验证有用,而不必提交反馈的形式)

  2. 的ProductVM得到在这一点上映射回ProductBE你会验证沿线的商业规则ValidationFactory.Validate(ProductBE),如果它无效返回消息查看并取消编辑,否则继续

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

2016编辑:除去Interface作为关注和接口分离的用途是完全正交的。

+0

该模式正在为我工​​作。所以总之,ViewModels应该是“物业包装”,其余的Model类提供业务规则来利用它们(并保持控制器的瘦身)? – med4th 2010-11-12 09:19:00

+1

最近已经将使用CodeSmith的应用程序迁移到Linq2Sql,该SOC已经削减了数周。这真是太遗憾了,我太新了,在这里碰到你的分数;-) – med4th 2010-11-12 18:21:19

0

你的问题就在这里:

public ActionResult Index(IContact enquiry) 

MVC的背景有可能创造一个具体类型调用时传递给方法。在这种方法的情况下,MVC需要创建一个实现IContract的类型。

哪种类型?我不知道。 MVC也没有。

为了能够模拟你的模型,而不是使用接口,而是使用普通的类来保护可以在mock中重写的方法。

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

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

public class MockContact 
{ 
    //some shared properties... 
    public string Name { get; set; } 
    public bool EmailSent {get;private set;} 

    public override void SendEmail(NameValueCollection vars = null) 
    { 
     EmailSent = true; 
    } 
} 

public ActionResult Index(Contact enquiry) 
+0

感谢您的快速回复,Will。 MockContact与联系人有什么关系?难道他们不得不相关,为了既能传入controller.Index(模型)? – med4th 2010-11-11 18:00:08