[英]Having problems with an ASP.NET MVC unit test & HttpContext.Current.Cache
[英]Unit test HttpContext.Current.Cache or other server-side methods in C#?
为使用HttpContext.Current.Cache
类的类创建单元测试时,使用NUnit时出现错误。 该功能是基本功能-检查项目是否在缓存中,如果没有,请创建并将其放入:
if (HttpContext.Current.Cache["Some_Key"] == null) {
myObject = new Object();
HttpContext.Current.Cache.Insert("Some_Key", myObject);
}
else {
myObject = HttpContext.Current.Cache.Get("Some_Key");
}
从单元测试调用此NullReferenceException
时,遇到第一个Cache
行时,它将失败并显示NullReferenceException
。 在Java中,我将使用Cactus测试服务器端代码。 我可以将类似的工具用于C#代码吗? 这个SO问题提到了模拟框架-这是我测试这些方法的唯一方法吗? 有没有类似的工具可以为C#运行测试?
另外,我不检查Cache
是否为空,因为我不想专门为单元测试编写代码,并假定在服务器上运行时它将始终有效。 这是否有效,还是应该在缓存周围添加空检查?
这样做的方法是避免直接使用HttpContext或其他类似的类,并用模拟代替它们。 毕竟,您不是要测试HttpContext是否正常运行(这是Microsoft的工作),而是只是测试这些方法在应有的时候被调用了。
步骤(如果您只是想了解该技术而又不涉及大量博客):
创建一个接口,该接口描述要在缓存中使用的方法(可能是诸如GetItem,SetItem,ExpireItem之类的东西)。 称它为ICache或任何你喜欢的东西
创建一个实现该接口的类,并将方法传递给实际的HttpContext
创建一个实现相同接口的类,就像一个模拟缓存一样。 如果您想保存对象,它可以使用字典或类似的东西
更改您的原始代码,使其完全不使用HttpContext,而只使用ICache。 然后,代码将需要获取ICache的实例-您可以在类构造函数中传递实例(这就是依赖注入的全部内容),或将其粘贴在某些全局变量中。
在生产应用程序中,将ICache设置为真正的HttpContext-Backed-Cache,在单元测试中,将ICache设置为模拟缓存。
利润!
我同意其他人的观点,即使用接口是最好的选择,但是有时更改现有系统只是不可行的。 这是我刚刚从我的一个项目中合并在一起的一些代码,这些代码应该可以为您提供所需的结果。 这是一个不错的解决方案,它是最遥远的事情,但是如果您真的无法更改代码,那么它应该可以完成工作。
using System;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Web;
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;
[TestFixture]
public class HttpContextCreation
{
[Test]
public void TestCache()
{
var context = CreateHttpContext("index.aspx", "http://tempuri.org/index.aspx", null);
var result = RunInstanceMethod(Thread.CurrentThread, "GetIllogicalCallContext", new object[] { });
SetPrivateInstanceFieldValue(result, "m_HostContext", context);
Assert.That(HttpContext.Current.Cache["val"], Is.Null);
HttpContext.Current.Cache["val"] = "testValue";
Assert.That(HttpContext.Current.Cache["val"], Is.EqualTo("testValue"));
}
private static HttpContext CreateHttpContext(string fileName, string url, string queryString)
{
var sb = new StringBuilder();
var sw = new StringWriter(sb);
var hres = new HttpResponse(sw);
var hreq = new HttpRequest(fileName, url, queryString);
var httpc = new HttpContext(hreq, hres);
return httpc;
}
private static object RunInstanceMethod(object source, string method, object[] objParams)
{
var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
var type = source.GetType();
var m = type.GetMethod(method, flags);
if (m == null)
{
throw new ArgumentException(string.Format("There is no method '{0}' for type '{1}'.", method, type));
}
var objRet = m.Invoke(source, objParams);
return objRet;
}
public static void SetPrivateInstanceFieldValue(object source, string memberName, object value)
{
var field = source.GetType().GetField(memberName, BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
if (field == null)
{
throw new ArgumentException(string.Format("Could not find the private instance field '{0}'", memberName));
}
field.SetValue(source, value);
}
}
HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
有一种更新的方法可以帮助专门处理单元测试中的缓存。
我建议使用Microsoft的新MemoryCache.Default方法。 您将需要使用.NET Framework 4.0或更高版本,并包括对System.Runtime.Caching的引用。
在此处查看文章-> http://msdn.microsoft.com/zh-cn/library/dd997357(v=vs.100).aspx
MemoryCache.Default适用于Web和非Web应用程序。 因此,您的想法是更新Web应用程序以删除对HttpContext.Current.Cache的引用,并将其替换为对MemoryCache.Default的引用。 稍后,当您运行决定对这些相同的方法进行单元测试时,缓存对象仍然可用,并且不会为空。 (因为它不依赖于HttpContext。)
这样,您甚至不必模拟缓存组件。
普遍的共识似乎是,在单元测试中驱动与HttpContext相关的任何事情都是一场噩梦,应该尽可能避免。
我认为您在模拟方面走了正确的路。 我喜欢RhinoMocks( http://ayende.com/projects/rhino-mocks.aspx )。
我也阅读了有关MoQ的一些好东西( http://code.google.com/p/moq ),尽管我还没有尝试过。
如果您真的想用C#编写可单元测试的Web UI,人们似乎会使用MVC框架( http://www.asp.net/mvc )而不是WebForms。
您可以在System.Web.Abstractions.dll中使用HttpContextBase类。 这是.NET 3.5中的新dll。
您可以在下面的链接中找到如何使用的示例。
如果您不关心测试缓存,则可以执行以下操作:
[TestInitialize]
public void TestInit()
{
HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
}
你也可以像下面一样起订量
var controllerContext = new Mock<ControllerContext>();
controllerContext.SetupGet(p => p.HttpContext.Session["User"]).Returns(TestGetUser);
controllerContext.SetupGet(p => p.HttpContext.Request.Url).Returns(new Uri("http://web1.ml.loc"));
对于那些使用MVC 3和MOQ的示例:
我的控制器方法有以下几行:
model.Initialize(HttpContext.Cache[Constants.C_CustomerTitleList]
as Dictionary<int, string>);
因此,任何单元测试都将失败,因为我没有设置HttpContext.Cache。
在单元测试中,我安排如下:
HttpRuntime.Cache[Constants.C_CustomerTitleList] = new Dictionary<int, string>();
var mockRequest = new Mock<HttpRequestBase>();
mockRequest.SetupGet(m => m.Url).Returns(new Uri("http://localhost"));
var context = new Mock<HttpContextBase>(MockBehavior.Strict);
context.SetupGet(x => x.Request).Returns(mockRequest.Object);
context.SetupGet(x => x.Cache).Returns(HttpRuntime.Cache);
var controllerContext = new Mock<ControllerContext>();
controllerContext.SetupGet(x => x.HttpContext).Returns(context.Object);
customerController.ControllerContext = controllerContext.Object;
可以尝试...
Isolate.WhenCalled(() => HttpContext.Current).ReturnRecursiveFake();
var fakeSession = HttpContext.Current.Session;
Isolate.WhenCalled(() => fakeSession.SessionID).WillReturn("1");
所有这些编程问题都要求一个基于接口的编程模型,在该模型中您需要两次实现该接口。 一种用于真实代码,另一种用于模型。
实例化是下一个问题。 有几种设计模式可用于此目的。 例如,参见著名的GangOfFour Creational模式( GOF )或Dependency Injection模式。
实际上,ASP.Net MVC使用的是这种基于接口的方法,因此更适合于单元测试。
就像每个人都说的那样, HTTPContext存在一个问题,当前Typemock是唯一可以直接伪造它而无需任何包装或抽象的框架。
缓存对象很难模拟,因为它是.NET框架的密封区域。 我通常通过构建一个接受缓存管理器对象的缓存包装器类来解决此问题。 为了进行测试,我使用了模拟缓存管理器; 在生产中,我使用一个实际上访问HttpRuntime.Cache的缓存管理器。
基本上,我自己提取缓存。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.