-
众所周知 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.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: }.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 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: response102: .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: response122: .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: }.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.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: }.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.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: }.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原理类似。