测试MVC之Mock HttpContext

来源:岁月联盟 编辑:exp 时间:2011-07-25

 

在Web 中进行测试驱动的开发,比较大的困难是模拟HttpContext ,它太复杂了。

moq 框架提供了强大的模拟能力,但是,模拟一个HttpContext 对象还是需要自己来动手。

为此,我自己写了一个方法来完成这个工作。其中,还顺便使用Log4Net 来输出一下它的工作情况。

view sourceprint?

01    /// <summary>

02    /// 创建上下文模拟对象 

03    /// 至少需要支持

04    ///     Request 中

05    ///         AppRelativeCurrentExecutionFilePath,

06    ///         ApplicationPath

07    ///         PathInfo

08    ///     Response 中

09    ///         ApplyAppPathModifier

10    /// </summary>

11    /// <returns></returns>

12    private Moq.Mock<System.Web.HttpContextBase> CreateHttpContext()

13    {

14        log4net.ILog log = log4net.LogManager.GetLogger("CreateHttpContext");

15     

16        string ApplicationPath = "/";

17        string PathInfo = "";

18        string AppRelativeCurrentExecutionFilePath = "~/";

19     

20        var contextMock = new Moq.Mock<System.Web.HttpContextBase>();

21     

22        contextMock

23            .Setup(c => c.Request.AppRelativeCurrentExecutionFilePath)

24            .Returns(AppRelativeCurrentExecutionFilePath)

25            .Callback(() => log.Info("Calling AppRelativeCurrentExecutionFilePath"));

26     

27        contextMock

28            .Setup(c => c.Request.ApplicationPath)

29            .Returns(ApplicationPath)

30            .Callback(() => log.Info("Calling ApplicationPath"));

31        contextMock.Setup(rc => rc.Request.PathInfo)

32            .Returns(PathInfo)

33            .Callback(() => log.Info("Calling PathInfo"));

34     

35        contextMock

36            .Setup(rc => rc.Response.ApplyAppPathModifier(Moq.It.IsAny<string>()))

37            .Returns((string s) => s)

38            .Callback((string s) => log.InfoFormat("Calling ApplyAppPathModifier: {0}.", s));

39     

40        return contextMock;

41    }

虽然这个方法已经能够完成我需要的测试,但是,我希望能将它提炼一下,得到一个更加通用的Mock 方法。

很快,我发现这个工作已经在很久以前被Scott Hanselman 介绍过一次了,其中甚至还写了不同的Mock 框架下的提供方法。不过moq 版本的作者不是他,而是另外一个人Daniel Cazzulino, 这篇文章你可以在这里找到:http://www.hanselman.com/blog/ASPNETMVCSessionAtMix08TDDAndMvcMockHelpers.aspx,还可以顺便看一下Daniel Cazzulino 的博客:http://blogs.clariusconsulting.net/kzu/

不过,他的文章是在2008 年写的,实在太古老了。当时还是MVC 的Preview2 ,moq 在那个时候就已经诞生了,看来我太孤陋寡闻了。

回到我们的主题,经过了这么长的时间,moq 已经有了一些变化,使用最新的moq 语法修改之后,发现他的的代码没有能够通过测试。

与我的比较一下,发现其中少了对Response 中ApplyAppPathModifier 方法的支持,增加之后就正常了。

下面是修改之后的代码,希望能给你带来方便。

view sourceprint?

001  using System;

002  using System.Web;

003  using System.Text.RegularExpressions;

004  using System.IO;

005  using System.Collections.Specialized;

006  using System.Web.Mvc;

007  using System.Web.Routing;

008   

009  using Moq;

010   

011  namespace UnitTests

012  {

013      public static class MvcMockHelpers

014      {

015          public static HttpContextBase FakeHttpContext()

016          {

017              var context = new Mock<HttpContextBase>();

018              var request = new Mock<HttpRequestBase>();

019              var response = new Mock<HttpResponseBase>();

020              var session = new Mock<HttpSessionStateBase>();

021              var server = new Mock<HttpServerUtilityBase>();

022   

023              // 必须要设置Response 的ApplyAppPathModifier 方法支持

024              response

025                  .Setup(rsp => rsp.ApplyAppPathModifier(Moq.It.IsAny<string>()))

026                  .Returns((string s) => s);

027                   

028              context.Setup(ctx => ctx.Request).Returns(request.Object);

029              context.Setup(ctx => ctx.Response).Returns(response.Object);

030              context.Setup(ctx => ctx.Session).Returns(session.Object);

031              context.Setup(ctx => ctx.Server).Returns(server.Object);

032   

033              return context.Object;

034          }

035   

036          public static HttpContextBase FakeHttpContext(string url)

037          {

038              HttpContextBase context = FakeHttpContext();

039              context.Request.SetupRequestUrl(url);

040              return context;

041          }

042   

043          // Controller 的一个扩展方法

044          public static void SetFakeControllerContext(this Controller controller)

045          {

046              var httpContext = FakeHttpContext();

047              ControllerContext context = new ControllerContext(new RequestContext(httpContext, new RouteData()), controller);

048              controller.ControllerContext = context;

049          }

050   

051          static string GetUrlFileName(string url)

052          {

053              if (url.Contains("?"))

054                  return url.Substring(0, url.IndexOf("?"));

055              else

056                  return url;

057          }

058   

059          static NameValueCollection GetQueryStringParameters(string url)

060          {

061              if (url.Contains("?"))

062              {

063                  NameValueCollection parameters = new NameValueCollection();

064   

065                  string[] parts = url.Split("?".ToCharArray());

066                  string[] keys = parts[1].Split("&".ToCharArray());

067   

068                  foreach (string key in keys)

069                  {

070                      string[] part = key.Split("=".ToCharArray());

071                      parameters.Add(part[0], part[1]);

072                  }

073   

074                  return parameters;

075              }

076              else

077              {

078                  return null;

079              }

080          }

081   

082          // 扩展HttpRequestBase

083          public static void SetHttpMethodResult(this HttpRequestBase request, string httpMethod)

084          {

085              Mock.Get(request)

086                  .Setup(req => req.HttpMethod)

087                  .Returns(httpMethod);

088          }

089   

090          // 设置请求的地址

091          public static void SetupRequestUrl(this HttpRequestBase request, string url)

092          {

093              log4net.ILog log = log4net.LogManager.GetLogger("CreateHttpContext");

094   

095              if (url == null)

096                  throw new ArgumentNullException("url");

097   

098              if (!url.StartsWith("~/"))

099                  throw new ArgumentException("Sorry, we expect a virtual url starting with /"~//".");

100   

101              var mock = Mock.Get(request);

102   

103              mock.Setup(req => req.QueryString)

104                  .Returns(GetQueryStringParameters(url));

105              mock.Setup(req => req.AppRelativeCurrentExecutionFilePath)

106                  .Returns(GetUrlFileName(url))

107                  .Callback(() => log.Info("Calling AppRelativeCurrentExecutionFilePath"));

108   

109   

110              mock.Setup(req => req.PathInfo)

111                  .Returns(string.Empty);

112   

113          }

114    

115      }

116  }