简体   繁体   English

单元测试HttpContext.Current.Cache或C#中的其他服务器端方法?

[英]Unit test HttpContext.Current.Cache or other server-side methods in C#?

When creating a unit test for a class that uses the HttpContext.Current.Cache class , I get an error when using NUnit. 为使用HttpContext.Current.Cache的类创建单元测试时,使用NUnit时出现错误。 The functionality is basic - check if an item is in the cache, and if not, create it and put it in: 该功能是基本功能-检查项目是否在缓存中,如果没有,请创建并将其放入:

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");
}

When calling this from a unit test, it fails with at NullReferenceException when encountering the first Cache line. 从单元测试调用此NullReferenceException时,遇到第一个Cache行时,它将失败并显示NullReferenceException In Java, I would use Cactus to test server-side code. 在Java中,我将使用Cactus测试服务器端代码。 Is there a similar tool I can use for C# code? 我可以将类似的工具用于C#代码吗? This SO question mentions mock frameworks - is this the only way I can test these methods? 这个SO问题提到了模拟框架-这是我测试这些方法的唯一方法吗? Is there a similar tool to run tests for C#? 有没有类似的工具可以为C#运行测试?

Also, I don't check if the Cache is null as I don't want to write code specifically for the unit test and assume it will always be valid when running on a server. 另外,我不检查Cache是否为空,因为我不想专门为单元测试编写代码,并假定在服务器上运行时它将始终有效。 Is this valid, or should I add null checks around the cache? 这是否有效,还是应该在缓存周围添加空检查?

The way to do this is to avoid direct use of the HttpContext or other similar classes, and substitute them with mocks. 这样做的方法是避免直接使用HttpContext或其他类似的类,并用模拟代替它们。 After all, you're not trying to test that the HttpContext functions properly (that's microsoft's job), you're just trying to test that the methods got called when they should have. 毕竟,您不是要测试HttpContext是否正常运行(这是Microsoft的工作),而是只是测试这些方法在应有的时候被调用了。

Steps (In case you just want to know the technique without digging through loads of blogs): 步骤(如果您只是想了解该技术而又不涉及大量博客):

  1. Create an interface which describes the methods you want to use in your caching (probably things like GetItem, SetItem, ExpireItem). 创建一个接口,该接口描述要在缓存中使用的方法(可能是诸如GetItem,SetItem,ExpireItem之类的东西)。 Call it ICache or whatever you like 称它为ICache或任何你喜欢的东西

  2. Create a class which implements that interface, and passes methods through to the real HttpContext 创建一个实现该接口的类,并将方法传递给实际的HttpContext

  3. Create a class which implements the same interface, and just acts like a mock cache. 创建一个实现相同接口的类,就像一个模拟缓存一样。 It can use a Dictionary or something if you care about saving objects 如果您想保存对象,它可以使用字典或类似的东西

  4. Change your original code so it doesn't use the HttpContext at all, and instead only ever uses an ICache. 更改您的原始代码,使其完全不使用HttpContext,而只使用ICache。 The code will then need to get an instance of the ICache - you can either pass an instance in your classes constructor (this is all that dependency injection really is), or stick it in some global variable. 然后,代码将需要获取ICache的实例-您可以在类构造函数中传递实例(这就是依赖注入的全部内容),或将其粘贴在某些全局变量中。

  5. In your production app, set the ICache to be your real HttpContext-Backed-Cache, and in your unit tests, set the ICache to be the mock cache. 在生产应用程序中,将ICache设置为真正的HttpContext-Backed-Cache,在单元测试中,将ICache设置为模拟缓存。

  6. Profit! 利润!

I agree with the others that using an interface would be the best option but sometimes it's just not feasible to change an existing system around. 我同意其他人的观点,即使用接口是最好的选择,但是有时更改现有系统只是不可行的。 Here's some code that I just mashed together from one of my projects that should give you the results you're looking for. 这是我刚刚从我的一个项目中合并在一起的一些代码,这些代码应该可以为您提供所需的结果。 It's the farthest thing from pretty or a great solution but if you really can't change your code around then it should get the job done. 这是一个不错的解决方案,它是最遥远的事情,但是如果您真的无法更改代码,那么它应该可以完成工作。

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));

If you're using .NET 3.5, you can use System.Web.Abstractions in your application. 如果使用的是.NET 3.5,则可以在应用程序中使用System.Web.Abstractions。

Justin Etheredge has a great post on how to mock HttpContext (which contains the cache class). 贾斯汀·埃瑟里奇(Justin Etheredge)在有关如何模拟HttpContext(包含缓存类)的文章中写了一篇很棒的文章

From Justin's example, I pass an HttpContextBase to my controllers using the HttpContextFactory.GetHttpContext. 从贾斯汀的示例中,我使用HttpContextFactory.GetHttpContext将HttpContextBase传递给控制器​​。 When mocking them, I just build a Mock to make calls to the cache object. 模拟它们时,我只是构建一个Mock来调用缓存对象。

There is a newer approach to help deal specifically with Cache in unit tests. 有一种更新的方法可以帮助专门处理单元测试中的缓存。

I would recommend using Microsoft's new MemoryCache.Default approach. 我建议使用Microsoft的新MemoryCache.Default方法。 You will need to use .NET Framework 4.0 or later and include a reference to System.Runtime.Caching. 您将需要使用.NET Framework 4.0或更高版本,并包括对System.Runtime.Caching的引用。

See article here --> http://msdn.microsoft.com/en-us/library/dd997357(v=vs.100).aspx 在此处查看文章-> http://msdn.microsoft.com/zh-cn/library/dd997357(v=vs.100).aspx

MemoryCache.Default works for both web and non-web applications. MemoryCache.Default适用于Web和非Web应用程序。 So the idea is you update your webapp to remove references to HttpContext.Current.Cache and replace them with references to MemoryCache.Default. 因此,您的想法是更新Web应用程序以删除对HttpContext.Current.Cache的引用,并将其替换为对MemoryCache.Default的引用。 Later, when you run decide to Unit Test these same methods, the cache object is still available and won't be null. 稍后,当您运行决定对这些相同的方法进行单元测试时,缓存对象仍然可用,并且不会为空。 (Because it is not reliant on an HttpContext.) (因为它不依赖于HttpContext。)

This way you don't even necessarily need to mock the cache component. 这样,您甚至不必模拟缓存组件。

General consensus seems to be that driving anything HttpContext related from within a unit test is a total nightmare, and should be avoided if possible. 普遍的共识似乎是,在单元测试中驱动与HttpContext相关的任何事情都是一场噩梦,应该尽可能避免。

I think you're on the right path regarding mocking. 我认为您在模拟方面走了正确的路。 I like RhinoMocks ( http://ayende.com/projects/rhino-mocks.aspx ). 我喜欢RhinoMocks( http://ayende.com/projects/rhino-mocks.aspx )。

I've read some good things about MoQ too ( http://code.google.com/p/moq ), although I've not tried it yet. 我也阅读了有关MoQ的一些好东西( http://code.google.com/p/moq ),尽管我还没有尝试过。

If you really want to write unit testable web UIs in C#, the way people seem to be heading is to use the MVC framework ( http://www.asp.net/mvc ) rather than WebForms... 如果您真的想用C#编写可单元测试的Web UI,人们似乎会使用MVC框架( http://www.asp.net/mvc )而不是WebForms。

You can use HttpContextBase class in System.Web.Abstractions.dll. 您可以在System.Web.Abstractions.dll中使用HttpContextBase类。 It's a new dll in .NET 3.5. 这是.NET 3.5中的新dll。

You can find an example how to use in the link below. 您可以在下面的链接中找到如何使用的示例。

http://vkreynin.wordpress.com/2009/03/23/stub-htttpcontext/ http://vkreynin.wordpress.com/2009/03/23/stub-htttpcontext/

if you dont care about testing cache you can do below: 如果您不关心测试缓存,则可以执行以下操作:

[TestInitialize]
    public void TestInit()
    {
      HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
    }

Also you can moq like below 你也可以像下面一样起订量

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"));

This maybe up your street ... Phil Haack shows off, with the help of Rhino mocks, how to mock httpcontext in asp mvc but I'd imagine can be applied to webforms. 这可能在您的大街上。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

Clicky!! Clicky!

Hope this helps. 希望这可以帮助。

An example for those using MVC 3 and MOQ: 对于那些使用MVC 3和MOQ的示例:

My controller method has the following line: 我的控制器方法有以下几行:

model.Initialize(HttpContext.Cache[Constants.C_CustomerTitleList] 
as Dictionary<int, string>);

As such, any unit test will fail as I'm not setting up HttpContext.Cache. 因此,任何单元测试都将失败,因为我没有设置HttpContext.Cache。

In my unit test, I arrange as follows: 在单元测试中,我安排如下:

 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;

Could try... 可以尝试...

 Isolate.WhenCalled(() => HttpContext.Current).ReturnRecursiveFake();
 var fakeSession = HttpContext.Current.Session;
 Isolate.WhenCalled(() => fakeSession.SessionID).WillReturn("1");

All of these programming questions ask for an interface based programming model, in which you implement the interface twice. 所有这些编程问题都要求一个基于接口的编程模型,在该模型中您需要两次实现该接口。 One for the real code and one for the mockup. 一种用于真实代码,另一种用于模型。

Instantiation is then the next issue. 实例化是下一个问题。 There are several design patterns that can be used for that. 有几种设计模式可用于此目的。 See for example the famous GangOfFour Creational patterns ( GOF ) or the Dependency Injection patterns. 例如,参见著名的GangOfFour Creational模式( GOF )或Dependency Injection模式。

ASP.Net MVC is in fact using this interface-based approach and is therefore much more suitable for unit testing. 实际上,ASP.Net MVC使用的是这种基于接口的方法,因此更适合于单元测试。

就像每个人都说的那样, HTTPContext存在一个问题,当前Typemock是唯一可以直接伪造它而无需任何包装或抽象的框架。

The cache object is difficult to mock because it's a sealed area of the .NET framework. 缓存对象很难模拟,因为它是.NET框架的密封区域。 I typically get around this by building a cache wrapper class that accepts a cache manager object. 我通常通过构建一个接受缓存管理器对象的缓存包装器类来解决此问题。 For testing, I use a mock cache manager; 为了进行测试,我使用了模拟缓存管理器; for production I use a cache manager that actually accesses HttpRuntime.Cache. 在生产中,我使用一个实际上访问HttpRuntime.Cache的缓存管理器。

Basically, I abstract away the cache myself. 基本上,我自己提取缓存。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM