[转]模拟HttpContext 实现ASP.NET MVC 的单元测试

时间:2021-04-16 13:29:53
  • 众所周知 ASP.NET MVC 的一个显著优势即可以很方便的实现单元测试,但在我们测试过程中经常要用到HttpContext,而默认情况下单元测试框架是不提供HttpContext的模拟的,本文通过MOQ框架实现对HttpContext的模拟从而实现更便利的单元测试。

    一、Moq框架使用

    Moq是一个非常优秀的模拟框架,可以实现对接口成员的模拟,常用在TDD中。 可在此处下载http://code.google.com/p/moq/downloads/list 也可以通过Nuget直接下载。

    先来看一个简单的moq应用

    1. 定义一个简单接口且不需要实现接口(Moq就是模拟框架因此不需要实现)

       1:  using System;
       2:   using System.Collections.Generic;
       3:   using System.Linq;
       4:   using System.Text;
       5:   
       6:   namespace Stphen.Sample.<a href="http://www.it165.net/pro/webasp/" target="_blank" class="keylink">ASP</a>NETMvc.MockUnitTest.Bussiness
       7:   {
       8:      public interface IFoo
       9:      {
      10:          void DoSomeThing(string thingName);
      11:          bool IsLoveFoo();
      12:   
      13:          string FooName { get; set; }
      14:      }
      15:   }

    .csharpcode, .csharpcode pre
    {
    font-size: small;
    color: black;
    font-family: consolas, "Courier New", courier, monospace;
    background-color: #ffffff;
    /*white-space: pre;*/
    }
    .csharpcode pre { margin: 0em; }
    .csharpcode .rem { color: #008000; }
    .csharpcode .kwrd { color: #0000ff; }
    .csharpcode .str { color: #006080; }
    .csharpcode .op { color: #0000c0; }
    .csharpcode .preproc { color: #cc6633; }
    .csharpcode .asp { background-color: #ffff00; }
    .csharpcode .html { color: #800000; }
    .csharpcode .attr { color: #ff0000; }
    .csharpcode .alt
    {
    background-color: #f4f4f4;
    width: 100%;
    margin: 0em;
    }
    .csharpcode .lnum { color: #606060; }

  • 2. 在测试代码中我实现了对接口的Mock

   1:  using System;
   2:  using System.Text;
   3:  using System.Collections.Generic;
   4:  using System.Linq;
   5:  using Microsoft.VisualStudio.TestTools.UnitTesting;
   6:  using Moq;
   7:  using Stphen.Sample.<a href="http://www.it165.net/pro/webasp/" target="_blank" class="keylink">ASP</a>NETMvc.MockUnitTest.Bussiness;
   8:   
   9:  namespace Stephen.Sample.ASPNETMvc.MockUnitTest.TestProject
  10:  {
  11:      [TestClass]
  12:      public class MockUnitTest
  13:      {
  14:          private readonly Mock<IFoo> _fooMock; 
  15:   
  16:          public MockUnitTest()
  17:          {
  18:              _fooMock = new Mock<IFoo>();
  19:          }
  20:   
  21:          [TestMethod]
  22:          public void MockDoSomeThingMethodTest()
  23:          {
  24:              _fooMock.Setup(foo => foo.DoSomeThing(It.IsAny<string>())).Callback((string s) => Console.WriteLine(s));
  25:              _fooMock.Object.DoSomeThing("HelloWorld");
  26:          }
  27:   
  28:          [TestMethod]
  29:          public  void MockIsLoveMockFramewrokMethodTest()
  30:          {
  31:              _fooMock.Setup(foo => foo.IsLoveFoo()).Returns(true);
  32:              Assert.AreEqual(true, _fooMock.Object.IsLoveFoo());
  33:          }
  34:   
  35:          [TestMethod]
  36:          public  void MockFooNamePropertyTest()
  37:          {
  38:              _fooMock.Setup(foo => foo.FooName).Returns("stephen");
  39:              Assert.AreEqual("stephen",_fooMock.Object.FooName);
  40:          }
  41:      }
  42:  }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

第一个方法没有返回值,第二个有返回值分别用Mock 的 Callback 和 return 方法。

二、通过Moq框架实现HttpContext的模拟(MvcContextMock)

   1:  using System;
   2:   using System.Collections.Generic;
   3:   using System.Linq;
   4:   using System.Text;
   5:   using System.Web;
   6:   using System.Web.Routing;
   7:   using System.Web.Mvc;
   8:   using Moq;
   9:   namespace MvcContextMock
  10:   {
  11:       /// <summary>
  12:       /// Mvc Context 工厂
  13:       /// </summary>
  14:       public static class MvcContextMockFactory
  15:       {
  16:           private static ControllerContext controllerContext = null;
  17:           /// <summary>
  18:           /// 创建ControllerContext
  19:           /// </summary>
  20:           /// <param name="controller">Controller</param>
  21:           /// <returns></returns>
  22:           public static ControllerContext CreateControllerContext(Controller controller)
  23:           {
  24:                controllerContext = new ControllerContext
  25:                  (
  26:                 CreateHttpContext(),
  27:                 new RouteData(),
  28:                  controller);
  29:               return controllerContext;
  30:           }
  31:   
  32:           /// <summary>
  33:           /// 创建ControllerContext
  34:           /// </summary>
  35:           /// <param name="controller">Controller</param>
  36:           /// <param name="contextBase">httpContextBase</param>
  37:           /// <returns></returns>
  38:           public static ControllerContext CreateControllerContext(Controller controller, HttpContextBase contextBase)
  39:           {
  40:                controllerContext = new ControllerContext
  41:                  (
  42:                 contextBase,
  43:                 new RouteData(),
  44:                  controller);
  45:               return controllerContext;
  46:           }
  47:   
  48:   
  49:           /// <summary>
  50:           /// 创建ControllerContext
  51:           /// </summary>
  52:           /// <param name="controller">controller</param>
  53:           /// <param name="url">访问的url地址</param>
  54:           /// <param name="httpMethod">访问的方法(get,post)</param>
  55:           /// <param name="name">路由名称</param>
  56:           /// <param name="pattern">路由格式</param>
  57:           /// <param name="obj">路由默认值</param>
  58:           /// <returns></returns>
  59:           public static ControllerContext CreateControllerContext(Controller controller, string url, string httpMethod, string name, string pattern, string obj)
  60:           {
  61:                controllerContext = new ControllerContext
  62:                   (
  63:                   CreateHttpContext(),
  64:                   GetRouteData(url, httpMethod, name, pattern, obj),
  65:                   controller);
  66:               return controllerContext;
  67:           }
  68:   
  69:           /// <summary>
  70:           /// 创建ControllerContext
  71:           /// </summary>
  72:           /// <param name="controller">controller</param>
  73:           /// <param name="contextBase">HttpContextBase</param>
  74:           /// <param name="url">访问的url地址</param>
  75:           /// <param name="httpMethod">访问的方法(get,post)</param>
  76:           /// <param name="name">路由名称</param>
  77:           /// <param name="pattern">路由格式</param>
  78:           /// <param name="obj">路由默认值</param>
  79:           /// <returns></returns>
  80:           public static ControllerContext CreateControllerContext(Controller controller, HttpContextBase contextBase, string url, string httpMethod, string name, string pattern, string obj)
  81:           {
  82:                controllerContext = new ControllerContext
  83:                   (
  84:                   contextBase,
  85:                   GetRouteData(url, httpMethod, name, pattern, obj),
  86:                   controller);
  87:               return controllerContext;
  88:           }
  89:   
  90:           /// <summary>
  91:           /// 创建HttpContextBase
  92:           /// </summary>
  93:           /// <returns></returns>
  94:           public static HttpContextBase CreateHttpContext()
  95:           {
  96:               var context = new Mock<HttpContextBase>();
  97:               var request = new Mock<HttpRequestBase>();
  98:               var response = new Mock<HttpResponseBase>();
  99:               var session = new Mock<HttpSessionStateBase>();
 100:               var server = new Mock<HttpServerUtilityBase>();
 101:               response
 102:                   .Setup(rsp => rsp.ApplyAppPathModifier(Moq.It.IsAny<string>()))
 103:                   .Returns((string s) => s);
 104:   
 105:               context.Setup(ctx => ctx.Request).Returns(request.Object);
 106:               context.Setup(ctx => ctx.Response).Returns(response.Object);
 107:               context.Setup(ctx => ctx.Session).Returns(session.Object);
 108:               context.Setup(ctx => ctx.Server).Returns(server.Object);
 109:   
 110:               return context.Object;
 111:           }
 112:   
 113:           #region Private Method
 114:           private static HttpContextBase CreateHttpContext(string url, string httpMethod)
 115:           {
 116:               var context = new Mock<HttpContextBase>();
 117:               var request = new Mock<HttpRequestBase>();
 118:               var response = new Mock<HttpResponseBase>();
 119:               var session = new Mock<HttpSessionStateBase>();
 120:               var server = new Mock<HttpServerUtilityBase>();
 121:               response
 122:                   .Setup(rsp => rsp.ApplyAppPathModifier(Moq.It.IsAny<string>()))
 123:                   .Returns((string s) => s);
 124:   
 125:               request.Setup(req => req.AppRelativeCurrentExecutionFilePath).Returns(url);
 126:               request.Setup(req => req.HttpMethod).Returns(httpMethod);
 127:   
 128:               context.Setup(ctx => ctx.Request).Returns(request.Object);
 129:               context.Setup(ctx => ctx.Response).Returns(response.Object);
 130:               context.Setup(ctx => ctx.Session).Returns(session.Object);
 131:               context.Setup(ctx => ctx.Server).Returns(server.Object);
 132:   
 133:               return context.Object;
 134:           }
 135:   
 136:           private static RouteData GetRouteData(string url, string httpMethod, string name, string pattern, string obj)
 137:           {
 138:               RouteTable.Routes.MapRoute(name, pattern, obj);
 139:               var routeData =
 140:                   RouteTable.Routes.
 141:                   GetRouteData(CreateHttpContext(url, httpMethod));
 142:               return routeData;
 143:           }
 144:           #endregion 
 145:   
 146:       }
 147:   }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

三、在mvc中使用MockHttpContextFactory

通过之前编写MvcContextMockFactory类我们在MVC单元测试中可以轻松的实现对HttpContext的模拟

首先创建一个控制器

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using System.Web.Mvc;
   6:  namespace Stephen.Sample.ASPNETMvc.MockUnitTest.Controller
   7:  {
   8:     public class HomeController:System.Web.Mvc.Controller
   9:      {
  10:        public ViewResult Index()
  11:        {
  12:            ViewData["controller"] = RouteData.Values["controller"];
  13:            ViewData["action"] = RouteData.Values["action"];
  14:            return View("Index");
  15:        }
  16:      }
  17:  }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

因为控制器中需要获取RouteData,RouteData来自于Controller下的ControllerContext

单元测试中调用MockHttpContextFactory模拟ControllerContext,完成单元测试。

   1:  using System;
   2:  using System.Text;
   3:  using System.Collections.Generic;
   4:  using System.Linq;
   5:  using System.Web.Mvc;
   6:  using Microsoft.VisualStudio.TestTools.UnitTesting;
   7:  using MvcContextMock;
   8:  using Stephen.Sample.ASPNETMvc.MockUnitTest.Controller;
   9:   
  10:  namespace Stephen.Sample.ASPNETMvc.MockUnitTest.TestProject
  11:  {
  12:      [TestClass]
  13:      public class HomeControllerUnitTest
  14:      {
  15:          [TestMethod]
  16:          public void IndexActionTest()
  17:          {
  18:              var homeController = new HomeController();
  19:              homeController.ControllerContext = MvcContextMock.MvcContextMockFactory.CreateControllerContext(homeController, "~/Home/Index", "get", "DefaultRoute", "{controller}/{action}", null);
  20:              ViewResult result= homeController.Index();
  21:              Assert.AreEqual("Index",result.ViewName);
  22:              Assert.AreEqual("Home",result.ViewData["controller"]);
  23:              Assert.AreEqual("Index", result.ViewData["action"]);
  24:          }
  25:      }
  26:  }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

对于更简单的HttpContext在MockHttpContextFactory中有专用的方法生成与ControllerContext原理类似。