众所周知 ASP.NET MVC 的一个显著优势即可以很方便的实现单元测试,但在我们测试过程中经常要用到HttpContext,而默认情况下单元测试框架是不提供HttpContext的模拟的,本文通过MOQ框架实现对HttpContext的模拟从而实现更便利的单元测试。
一、Moq框架使用
Moq是一个非常优秀的模拟框架,可以实现对接口成员的模拟,常用在TDD中。 可在此处下载http://code.google.com/p/moq/downloads/list 也可以通过Nuget直接下载。
先来看一个简单的moq应用
1. 定义一个简单接口且不需要实现接口(Moq就是模拟框架因此不需要实现)
class="alt"> 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: }
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.TestProject10: {
11: [TestClass]
12: public class MockUnitTest13: {
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: }
第一个方法没有返回值,第二个有返回值分别用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 MvcContextMock10: {
11: /// <summary>12: /// Mvc Context 工厂13: /// </summary>14: public static class MvcContextMockFactory15: {
16: private static ControllerContext controllerContext = null;17: /// <summary>18: /// 创建ControllerContext19: /// </summary>20: /// <param name="controller">Controller</param>21: /// <returns></returns>22: public static ControllerContext CreateControllerContext(Controller controller)23: {
24: controllerContext = new ControllerContext25: (
26: CreateHttpContext(),
27: new RouteData(),28: controller);
29: return controllerContext;30: }
31:
32: /// <summary>33: /// 创建ControllerContext34: /// </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 ControllerContext41: (
42: contextBase,
43: new RouteData(),44: controller);
45: return controllerContext;46: }
47:
48:
49: /// <summary>50: /// 创建ControllerContext51: /// </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 ControllerContext62: (
63: CreateHttpContext(),
64: GetRouteData(url, httpMethod, name, pattern, obj),
65: controller);
66: return controllerContext;67: }
68:
69: /// <summary>70: /// 创建ControllerContext71: /// </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 ControllerContext83: (
84: contextBase,
85: GetRouteData(url, httpMethod, name, pattern, obj),
86: controller);
87: return controllerContext;88: }
89:
90: /// <summary>91: /// 创建HttpContextBase92: /// </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 Method114: 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: #endregion145:
146: }
147: }
三、在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.Controller7: {
8: public class HomeController:System.Web.Mvc.Controller9: {
10: public ViewResult Index()11: {
12: ViewData["controller"] = RouteData.Values["controller"];13: ViewData["action"] = RouteData.Values["action"];14: return View("Index");15: }
16: }
17: }
因为控制器中需要获取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.TestProject11: {
12: [TestClass]
13: public class HomeControllerUnitTest14: {
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: }
对于更简单的HttpContext在MockHttpContextFactory中有专用的方法生成与ControllerContext原理类似。