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上,我得到一个"无法创建接口的实例"异常.我似乎无法拥有我的蛋糕(通过模型)并吃掉它(通过模拟进行单元测试).也许有更好的单元测试模型绑定到视图?
谢谢,
地中海
我要把它扔出去,如果你需要嘲笑你的模型你做错了.你的模特应该是愚蠢的财产袋.
您的模型绝对没有理由应该使用SendEmail方法.这是应该从调用EmailService的控制器调用的功能.
回答你的问题:
经过多年与分离关注(SOC)模式(如MVC,MVP,MVVM)的合作以及看到比我更聪明的人的文章(我希望我能找到一个我正在考虑这个但也许我在杂志上阅读的文章) .您最终将在企业应用程序中得出结论,最终将得到3组不同的模型对象.
以前,我是使用一组业务实体进行域驱动设计(DDD)的忠实粉丝,这些业务实体既是普通的旧c#对象(POCO)又是持久无知(PI).拥有POCO/PI的域模型会为您留下一块干净的对象,其中没有与访问对象存储相关的代码,或者只有代码的1个区域具有其他具有逻辑意义的属性.
虽然这有效,并且可以在一段时间内运行良好,但最终会出现一个转折点,即表达视图,域模型和物理存储模型之间关系的复杂性变得过于复杂,无法正确表达1组实体.
要解决View,Domain和Storage的阻抗不匹配问题,您需要3套模型.您的ViewModel将与您的视图绑定完全匹配,以便于使用UI.因此,这将经常包含诸如添加List以使用对编辑视图/操作有效的值填充下拉列表的内容.
中间是域实体,这些是您应根据业务规则进行验证的实体.因此,您将在视图的两侧与/或从存储层映射到/从它们映射.在这些实体中,您可以附加代码以进行验证.我个人不喜欢使用属性并将验证逻辑耦合到您的域实体中.将验证属性耦合到ViewModel中以利用内置的MVC客户端验证功能确实很有意义.
对于验证,我建议使用像FluentValidation(或您自己的自定义库,它们并不难写)的库,它允许您将业务规则与对象分开.虽然使用MVC3的新功能可以进行远程验证,并且可以显示客户端,但这是一个处理真正业务验证的选项.
最后你有你的存储模型.正如我之前所说,我非常热衷于让PI对象能够在所有层中重复使用,因此根据您设置持久存储的方式,您可以直接使用域对象.但是如果您利用Linq2Sql,EntityFramework(EF)等工具,您很可能会使用自动生成的模型,其中包含与数据提供程序交互的代码,因此您需要将域对象映射到持久性对象.
因此,将所有这些包装起来,这将是MVC操作中的标准逻辑流程
用户去编辑产品页面
EF查询数据库以获取现有产品信息,在存储库层内,EF数据对象被映射到业务实体(BE),因此所有数据层方法都返回BE并且没有与EF数据对象的外部耦合.(因此,如果您更改了数据提供程序,则除了内部实现之外,您不必更改任何代码行)
控制器获取产品BE并将其映射到产品ViewModel(VM),并为可以为下拉列表设置的不同选项添加集合
返回视图(theview,ProductVM)
用户编辑产品并提交表单
传递客户端验证(对于日期验证/数字验证很有用,而不必提交表单以供反馈)
此时,ProductVM将映射回ProductBE,您将验证业务规则ValidationFactory.Validate(ProductBE),如果它无效,则返回消息返回查看并取消编辑,否则继续
您将ProductBE传递到存储库模型,在将ProductBE映射到EF的产品数据实体的数据层的内部实现中,并更新数据库.
2016编辑:删除Interface了关注和界面分离完全正交的用法.
| 归档时间: |
|
| 查看次数: |
3217 次 |
| 最近记录: |