尽可能摆脱对HttpContext的依赖

  之前谈到对视图进行单元测试时,老赵曾经谈起在视图中应该只使用ViewData中的数据。这不是第一次说起要放弃HttpContext了,自从有了“抽象”这一有利武器后,一切“不和谐”因素都能够被分离。试想在MVP模式中,View和Presenter都使用各自的抽象进行交互,一切Web控件,HttpContext等对象都不复存在了,大家眼中只有“数据”和“模型”。同样,在ASP.NET MVC的Action方法中,也不应该使用HttpContext,这是基于良好的“可测试性”而考虑的。您可能会想,现在的HttpContextBase对象已经可以Mock了啊。没错,它的确“可以”,但是这样做会引起单元测试代码的膨胀,因为测试代码中的相当部分必须关注在测试数据的准备,而不是被测试的功能上。对于一个Action方法来说,它关注的应该是用户与业务逻辑的交互,而不是“如何把HTTP请求转化为可用的数据”。其实说到底,还是要“分离关注点”。

  在ASP.NET MVC中负责“转化数据”的层次为Model Binder。关于这一点,现有的“示例”大都关注把Form或QueryString中的数据转化为Action参数上,不过Model Binder可用的地方其实更多。例如在《最佳实践》的代码中,原本AccountController的Delete方法实现如下:

  public ActionResult Delete(string userName)

  {

  this.MiddleTier.UserManager.Delete(userName);

  Uri urlReferrer = this.Request.UrlReferrer;

  return this.Redirect(urlReferrer.ToString());

  }

  在删除了指定对象之后,页面将跳转到Url Referrer地址中。在上面的代码中,这个值将通过访问Request.UrlReferer来获得。这就使您的Action方法与HttpContext产生了依赖,因此它的单元测试代码就需要这样编写:

  [TestMethod]

  public void DeleteTest()

  {

  string userName = "jeffz";

  Uri urlReferrer = new Uri("http://www.microsoft.com");

  var mockHttpContext = new Mock();

  mockHttpContext.Setup(c => c.Request.UrlReferrer).Returns(urlReferrer);

  var mockController = this.GetMockController();

  mockController.Setup(c => c.MiddleTier.UserManager.Delete(userName)).Verifiable();

  mockController.Object.ControllerContext = new ControllerContext(

  mockHttpContext.Object, new RouteData(), mockController.Object);

  dxgb.info

  yhnj.info

  mockController.Object.Delete(userName)...

  }

  在单元测试代码中,我们Mock了一个HttpContextBase对象,让它的Request.UrlReferrer属性返回我们准备好的对象,再构造一个新的ControllerContext并交给Controller。而如果我们的UrlReferrer能够作为Delete方法的参数,那么单元测试代码就会一下子简单很多:

  [TestMethod()]

  public void DeleteTest()

  {

  string userName = "jeffz";

  Uri urlReferrer = new Uri("http://www.microsoft.com");

  var mockController = this.GetMockController();

  mockController.Setup(c => c.MiddleTier.UserManager.Delete(userName)).Verifiable();

  mockController.Object.Delete(userName, urlReferrer)...

  qwdg.info

  njmk.info

  }

  有些朋友可能会问,不就是从Request的UrlReferrer属性中取值吗?我们为什么要构造一个ControllerContext,不能直接设置Controller对象吗?例如这样就简单多了:

  mockController.Setup(c => c.Request.UrlReferrer).Returns(urlReferrer);

  似乎可行,不过您运行的时候就会发现,框架会抛出异常,说只有接口的成员,或可以override的成员才能够被Mock。没错,Controller的Request属性不是virtual的,无法override。Controller类如此设计是故意的,目的就是限制了可用的路径。试想,如果您Mock了Controller.Request属性,但是程序代码通过Controller.HttpContext.Request进行访问又怎么办呢?类似的做法还有对方法重载的设计。一般来说,都会把其中几个方法委托给其中唯一的方法,而只有那个方法是可以被override的。这样在编写测试时,我们仅有的Mock入口便确定了,避免了测试代码过度了解方法实现的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值