众所周知 ASP.NET MVC 的一个显著优势即可以很方便的实现单元测试,但在我们测试过程中经常要用到HttpContext,而默认情况下单元测试框架是不提供HttpContext的模拟的,本文通过MOQ框架实现对HttpContext的模拟从而实现更便利的单元测试。
一、Moq框架使用
Moq是一个非常优秀的模拟框架,可以实现对接口成员的模拟,常用在TDD中。 可在此处下载 http://code.google.com/p/moq/downloads/list 也可以通过Nuget直接下载。
先来看一个简单的moq应用
1. 定义一个简单接口且不需要实现接口(Moq就是模拟框架因此不需要实现)
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace Stphen.Sample.ASPNETMvc.MockUnitTest.Bussiness 7 { 8 public interface IFoo 9 { 10 void DoSomeThing(string thingName); 11 bool IsLoveFoo(); 12 13 string FooName { get; set; } 14 } 15 }
2. 在测试代码中我实现了对接口的Mock
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
using System; using System.Text; using System.Collections.Generic; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Stphen.Sample.ASPNETMvc.MockUnitTest.Bussiness; namespace Stephen.Sample.ASPNETMvc.MockUnitTest.TestProject { [TestClass] public class MockUnitTest { private readonly Mock<IFoo> _fooMock; public MockUnitTest() { _fooMock = new Mock<IFoo>(); } [TestMethod] public void MockDoSomeThingMethodTest() { _fooMock.Setup(foo => foo.DoSomeThing(It.IsAny<string>())).Callback((string s) => Console.WriteLine(s)); _fooMock.Object.DoSomeThing("HelloWorld"); } [TestMethod] public void MockIsLoveMockFramewrokMethodTest() { _fooMock.Setup(foo => foo.IsLoveFoo()).Returns(true); Assert.AreEqual(true, _fooMock.Object.IsLoveFoo()); } [TestMethod] public void MockFooNamePropertyTest() { _fooMock.Setup(foo => foo.FooName).Returns("stephen"); Assert.AreEqual("stephen",_fooMock.Object.FooName); } } }
第一个方法没有返回值,第二个有返回值分别用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 }
三、在mvc中使用MockHttpContextFactory
通过之前编写MvcContextMockFactory类我们在MVC单元测试中可以轻松的实现对HttpContext的模拟
首先创建一个控制器
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Web.Mvc; namespace Stephen.Sample.ASPNETMvc.MockUnitTest.Controller { public class HomeController:System.Web.Mvc.Controller { public ViewResult Index() { ViewData["controller"] = RouteData.Values["controller"]; ViewData["action"] = RouteData.Values["action"]; return View("Index"); } } }
因为控制器中需要获取RouteData,RouteData来自于Controller下的ControllerContext
单元测试中调用MockHttpContextFactory模拟ControllerContext,完成单元测试。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
using System; using System.Text; using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using Microsoft.VisualStudio.TestTools.UnitTesting; using MvcContextMock; using Stephen.Sample.ASPNETMvc.MockUnitTest.Controller; namespace Stephen.Sample.ASPNETMvc.MockUnitTest.TestProject { [TestClass] public class HomeControllerUnitTest { [TestMethod] public void IndexActionTest() { var homeController = new HomeController(); homeController.ControllerContext = MvcContextMock.MvcContextMockFactory.CreateControllerContext(homeController, "~/Home/Index", "get", "DefaultRoute", "{controller}/{action}", null); ViewResult result= homeController.Index(); Assert.AreEqual("Index",result.ViewName); Assert.AreEqual("Home",result.ViewData["controller"]); Assert.AreEqual("Index", result.ViewData["action"]); } } }
对于更简单的HttpContext在MockHttpContextFactory中有专用的方法生成与ControllerContext原理类似。