C# Moq:对依赖 HttpContext 的方法进行单元测试
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1214178/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Moq: unit testing a method relying on HttpContext
提问by p.campbell
Consider a method in a .NET assembly:
考虑 .NET 程序集中的一个方法:
public static string GetSecurityContextUserName()
{
//extract the username from request
string sUser = HttpContext.Current.User.Identity.Name;
//everything after the domain
sUser = sUser.Substring(sUser.IndexOf("\") + 1).ToLower();
return sUser;
}
I'd like to call this method from a unit test using the Moq framework. This assembly is part of a webforms solution. The unit test looks like this, but I am missing the Moq code.
我想从使用 Moq 框架的单元测试中调用此方法。此程序集是 webforms 解决方案的一部分。单元测试看起来像这样,但我缺少 Moq 代码。
//arrange
string ADAccount = "BUGSBUNNY";
string fullADName = "LOONEYTUNES\BUGSBUNNY";
//act
//need to mock up the HttpContext here somehow -- using Moq.
string foundUserName = MyIdentityBL.GetSecurityContextUserName();
//assert
Assert.AreEqual(foundUserName, ADAccount, true, "Should have been the same User Identity.");
Question:
问题:
- How can I use Moq to arrange a fake HttpContext object with some value like 'MyDomain\MyUser'?
- How do I associate that fake with my call into my static method at
MyIdentityBL.GetSecurityContextUserName()
? - Do you have any suggestions on how to improve this code/architecture?
- 我如何使用 Moq 来安排一个假的 HttpContext 对象,它有一些像“MyDomain\MyUser”这样的值?
- 我如何将这个伪造与我对静态方法的调用联系起来
MyIdentityBL.GetSecurityContextUserName()
? - 您对如何改进此代码/架构有任何建议吗?
采纳答案by womp
Webforms is notoriously untestable for this exact reason - a lot of code can rely on static classes in the asp.net pipeline.
由于这个确切的原因,Webforms 是出了名的不可测试 - 许多代码可以依赖于 asp.net 管道中的静态类。
In order to test this with Moq, you need to refactor your GetSecurityContextUserName()
method to use dependency injection with an HttpContextBase
object.
为了使用 Moq 进行测试,您需要重构您的GetSecurityContextUserName()
方法以对HttpContextBase
对象使用依赖注入。
HttpContextWrapper
resides in System.Web.Abstractions
, which ships with .Net 3.5. It is a wrapper for the HttpContext
class, and extends HttpContextBase
, and you can construct an HttpContextWrapper
just like this:
HttpContextWrapper
驻留在 中System.Web.Abstractions
,它随 .Net 3.5 一起提供。它是HttpContext
类的包装器,并且 extends HttpContextBase
,您可以HttpContextWrapper
像这样构造一个:
var wrapper = new HttpContextWrapper(HttpContext.Current);
Even better, you can mock an HttpContextBase and set up your expectations on it using Moq. Including the logged in user, etc.
更好的是,您可以模拟 HttpContextBase 并使用 Moq 设置您对它的期望。包括登录的用户等。
var mockContext = new Mock<HttpContextBase>();
With this in place, you can call GetSecurityContextUserName(mockContext.Object)
, and your application is much less coupled to the static WebForms HttpContext. If you're going to be doing a lot of tests that rely on a mocked context, I highly suggest taking a look at Scott Hanselman's MvcMockHelpers class, which has a version for use with Moq. It conveniently handles a lot of the setup necessary. And despite the name, you don't need to be doing it with MVC - I use it successfully with webforms apps when I can refactor them to use HttpContextBase
.
有了这个,您可以调用GetSecurityContextUserName(mockContext.Object)
,并且您的应用程序与静态 WebForms HttpContext 的耦合要小得多。如果您要进行大量依赖模拟上下文的测试,我强烈建议您查看 Scott Hanselman 的 MvcMockHelpers 类,它有一个用于 Moq 的版本。它可以方便地处理许多必要的设置。尽管名称如此,但您无需使用 MVC 进行操作 - 当我可以将它们重构为使用HttpContextBase
.
回答by Greg Beech
If you're using the CLR security model (as we do) then you'll need to use some abstracted functions to get and set the current principal if you want to allow testing, and use these whenever getting or setting the principal. Doing this allows you to get/set the principal wherever is relevant (typically on HttpContext
on the web, and on the current thread elsewhere like unit tests). This would look something like:
如果您正在使用 CLR 安全模型(正如我们所做的那样),那么您需要使用一些抽象函数来获取和设置当前主体(如果要允许测试),并在获取或设置主体时使用这些函数。这样做允许您在相关的任何地方获取/设置主体(通常在HttpContext
网络上,以及在其他地方的当前线程上,如单元测试)。这看起来像:
public static IPrincipal GetCurrentPrincipal()
{
return HttpContext.Current != null ?
HttpContext.Current.User :
Thread.CurrentThread.Principal;
}
public static void SetCurrentPrincipal(IPrincipal principal)
{
if (HttpContext.Current != null) HttpContext.Current.User = principal'
Thread.CurrentThread.Principal = principal;
}
If you use a custom principal then these can be fairly nicely integrated into its interface, for example below Current
would call GetCurrentPrincipal
and SetAsCurrent
would call SetCurrentPrincipal
.
如果您使用自定义主体,那么这些可以很好地集成到其界面中,例如下面Current
会调用GetCurrentPrincipal
和SetAsCurrent
会调用SetCurrentPrincipal
.
public class MyCustomPrincipal : IPrincipal
{
public MyCustomPrincipal Current { get; }
public bool HasCurrent { get; }
public void SetAsCurrent();
}
回答by Juri
This is not really related in using Moq for unit testing of what you need.
这与使用 Moq 对您需要的单元进行单元测试没有真正的关系。
Generally we at work have a layered architecture, where the code on the presentation layer is really just for arranging things for being displayed on the UI. This kind of code is not covered with unit tests. All the rest of the logic resides on the business layer, which doesn't have to have any dependency on the presentation layer (i.e. UI specific references such as the HttpContext) since the UI may also be a WinForms application and not necessarily a web application.
通常我们在工作中有一个分层的架构,其中表现层上的代码实际上只是为了安排在 UI 上显示的东西。此类代码不包含在单元测试中。所有其余的逻辑都驻留在业务层上,它不必依赖于表示层(即 UI 特定的引用,如 HttpContext),因为 UI 也可能是 WinForms 应用程序而不一定是 Web 应用程序.
In this way you can avoid to mess around with Mock frameworks, trying to simulate HttpRequests etc...although often it may still be necessary.
通过这种方式,您可以避免弄乱 Mock 框架,尝试模拟 HttpRequests 等......尽管通常它可能仍然是必要的。
回答by Sly Gryphon
In general for ASP.NET unit testing, rather than accessing HttpContext.Current you should have a property of type HttpContextBase whose value is set by dependency injection (such as in the answer provided by Womp).
一般来说,对于 ASP.NET 单元测试,而不是访问 HttpContext.Current 您应该拥有一个 HttpContextBase 类型的属性,其值由依赖注入设置(例如在 Womp 提供的答案中)。
However, for testing security related functions I would recommend using Thread.CurrentThread.Principal (instead of HttpContext.Current.User). Using Thread.CurrentThread has the advantage of also being reusable outside a web context (and works the same in a web context because the ASP.NET framework always sets both values the same).
但是,为了测试与安全相关的功能,我建议使用 Thread.CurrentThread.Principal(而不是 HttpContext.Current.User)。使用 Thread.CurrentThread 的优点是还可以在 Web 上下文之外重用(并且在 Web 上下文中的工作方式相同,因为 ASP.NET 框架始终将两个值设置为相同)。
To then test Thread.CurrentThread.Principal I usually use a scope class that sets the Thread.CurrentThread to a test value and then resets on dispose:
然后测试 Thread.CurrentThread.Principal 我通常使用范围类将 Thread.CurrentThread 设置为测试值,然后在处置时重置:
using (new UserResetScope("LOONEYTUNES\BUGSBUNNY")) {
// Put test here -- CurrentThread.Principal is reset when PrincipalScope is disposed
}
This fits well with the standard .NET security component -- where a component has a known interface (IPrincipal) and location (Thread.CurrentThread.Principal) -- and will work with any code that correctly uses/checks against Thread.CurrentThread.Principal.
这非常适合标准的 .NET 安全组件——其中一个组件具有已知的接口 (IPrincipal) 和位置 (Thread.CurrentThread.Principal)——并且将适用于任何正确使用/检查 Thread.CurrentThread.Principal 的代码.
A base scope class would be something like the following (adjust as necessary for things like adding roles):
基本范围类将类似于以下内容(根据添加角色之类的需要进行调整):
class UserResetScope : IDisposable {
private IPrincipal originalUser;
public UserResetScope(string newUserName) {
originalUser = Thread.CurrentPrincipal;
var newUser = new GenericPrincipal(new GenericIdentity(newUserName), new string[0]);
Thread.CurrentPrincipal = newUser;
}
public IPrincipal OriginalUser { get { return this.originalUser; } }
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) {
if (disposing) {
Thread.CurrentPrincipal = originalUser;
}
}
}
Another alternative is, instead of using the standard security component location, write your app to use injected security details, e.g. add an ISecurityContext property with a GetCurrentUser() method or similar, and then use that consistently throughout your application -- but if you are going to do this in the context of a web application then you might as well use the pre-built injected context, HttpContextBase.
另一种选择是,而不是使用标准的安全组件位置,编写您的应用程序以使用注入的安全细节,例如使用 GetCurrentUser() 方法或类似方法添加 ISecurityContext 属性,然后在整个应用程序中一致地使用它——但如果您是如果要在 Web 应用程序的上下文中执行此操作,那么您不妨使用预先构建的注入上下文 HttpContextBase。
回答by Davut Gürbüz
Have a look at this http://haacked.com/archive/2007/06/19/unit-tests-web-code-without-a-web-server-using-httpsimulator.aspx
Using httpSimulator class,You will be able to do pass a HttpContext to handler
使用 httpSimulator 类,您将能够将 HttpContext 传递给处理程序
HttpSimulator sim = new HttpSimulator("/", @"C:\intepub\?")
.SimulateRequest(new Uri("http://localhost:54331/FileHandler.ashx?
ticket=" + myticket + "&fileName=" + path));
FileHandler fh = new FileHandler();
fh.ProcessRequest(HttpContext.Current);
HttpSimulator implement what we need to get a HttpContext instance. So you don't need to use Moq here.
HttpSimulator 实现了获取 HttpContext 实例所需的内容。所以你不需要在这里使用 Moq。
回答by PUG
[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"));