Moq:对依赖 HttpContext 的方法进行单元测试
考虑 .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;
}
我想使用 Moq 框架从单元测试中调用此方法。 该程序集是 Webforms 解决方案的一部分。 单元测试看起来像这样,但我缺少起订量代码。
//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.");
问题:
- 我如何使用 Moq 来安排一个具有“MyDomain\MyUser”等值的假 HttpContext 对象?
- 如何将该假冒与对
MyIdentityBL.GetSecurityContextUserName()
处的静态方法的调用关联起来? - 您对如何改进此代码/架构有什么建议吗?
Consider a method in a .NET assembly:
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.
//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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
您也可以像下面这样起订量
Also you can moq like below
一般来说,对于 ASP.NET 单元测试,您应该拥有一个 HttpContextBase 类型的属性,而不是访问 HttpContext.Current,其值是通过依赖项注入设置的(例如 Womp 提供的答案)。
但是,为了测试安全相关的功能,我建议使用 Thread.CurrentThread.Principal (而不是 HttpContext.Current.User)。 使用 Thread.CurrentThread 的优点是还可以在 Web 上下文之外重用(并且在 Web 上下文中的工作方式相同,因为 ASP.NET 框架始终将两个值设置为相同)。
然后,为了测试 Thread.CurrentThread.Principal,我通常使用一个作用域类,将 Thread.CurrentThread 设置为测试值,然后在处置时重置:
这非常适合标准 .NET 安全组件——其中组件具有已知接口( IPrincipal) 和位置 (Thread.CurrentThread.Principal) - 并将与正确使用/检查 Thread.CurrentThread.Principal 的任何代码一起使用。
基本作用域类将类似于以下内容(根据需要进行调整,以添加角色等):
另一种选择是,不使用标准安全组件位置,而是编写应用程序以使用注入的安全详细信息,例如添加 ISecurityContext 属性和GetCurrentUser() 方法或类似方法,然后在整个应用程序中一致地使用该方法 - 但如果您要在 Web 应用程序的上下文中执行此操作,那么您也可以使用预构建的注入上下文 HttpContextBase。
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).
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).
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:
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.
A base scope class would be something like the following (adjust as necessary for things like adding roles):
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.
在 ASP.NET MVC Core 中,我使用以下代码来测试依赖于 HttpContext 的控制器:
这是一个示例单元测试:
In ASP.NET MVC Core I use the following code to test controllers, which depend on
HttpContext
:This is a sample unit test:
看看这个
http ://haacked.com/archive/2007/06/19/unit-tests-web-code-without-a-web-server-using-httpsimulator.aspx
使用 httpSimulator 类,您将能够做到将 HttpContext 传递给处理程序
HttpSimulator 实现我们获取 HttpContext 实例所需的内容。 所以你不需要在这里使用 Moq。
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 implement what we need to get a HttpContext instance. So you don't need to use Moq here.
如果您使用 CLR 安全模型(就像我们一样),那么如果您想允许测试,则需要使用一些抽象函数来获取和设置当前主体,并在获取或设置主体时使用这些函数。 这样做允许您在相关的地方获取/设置主体(通常在网络上的 HttpContext 上,以及在其他地方的当前线程上,如单元测试)。 这看起来像:
如果您使用自定义主体,那么它们可以很好地集成到其接口中,例如下面的
Current
将调用GetCurrentPrincipal
和SetAsCurrent< /code> 将调用
SetCurrentPrincipal
。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:If you use a custom principal then these can be fairly nicely integrated into its interface, for example below
Current
would callGetCurrentPrincipal
andSetAsCurrent
would callSetCurrentPrincipal
.这与使用 Moq 进行所需的单元测试并没有真正的关系。
一般来说,我们在工作中采用分层架构,其中表示层上的代码实际上只是用于安排在 UI 上显示的内容。 单元测试不涵盖此类代码。 所有其余逻辑都驻留在业务层上,业务层不必依赖于表示层(即特定于 UI 的引用,例如 HttpContext),因为 UI 也可能是 WinForms 应用程序,而不一定是 Web 应用程序。
通过这种方式,您可以避免使用 Mock 框架、尝试模拟 HttpRequest 等……尽管通常这可能仍然是必要的。
This is not really related in using Moq for unit testing of what you need.
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.
In this way you can avoid to mess around with Mock frameworks, trying to simulate HttpRequests etc...although often it may still be necessary.
由于这个确切原因,Webforms 是出了名的不可测试 - 许多代码可以依赖于 ASP.NET 管道中的静态类。
为了使用 Moq 进行测试,您需要重构
GetSecurityContextUserName()
方法,以使用HttpContextBase
对象的依赖注入。HttpContextWrapper
驻留在System.Web.Abstractions
中,它随 .Net 3.5 一起提供。 它是 HttpContext 类的包装器,并扩展了 HttpContextBase ,您可以像这样构造一个 HttpContextWrapper :更好的是,您可以模拟一个 HttpContextBase 并使用 Moq 设置您对它的期望。 包括登录的用户等。
完成此操作后,您可以调用 GetSecurityContextUserName(mockContext.Object) ,并且您的应用程序与静态 WebForms HttpContext 的耦合要少得多。 如果您要做大量依赖模拟上下文的测试,我强烈建议 看一下 Scott Hanselman 的 MvcMockHelpers 类,它有一个与 Moq 一起使用的版本。 它可以方便地处理许多必要的设置。 尽管有这个名称,但您不需要使用 MVC 来实现它 - 当我可以重构 Web 表单应用程序以使用
HttpContextBase
时,我可以成功地将其与 Web 表单应用程序一起使用。Webforms is notoriously untestable for this exact reason - a lot of code can rely on static classes in the asp.net pipeline.
In order to test this with Moq, you need to refactor your
GetSecurityContextUserName()
method to use dependency injection with anHttpContextBase
object.HttpContextWrapper
resides inSystem.Web.Abstractions
, which ships with .Net 3.5. It is a wrapper for theHttpContext
class, and extendsHttpContextBase
, and you can construct anHttpContextWrapper
just like this:Even better, you can mock an HttpContextBase and set up your expectations on it using Moq. Including the logged in user, etc.
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 useHttpContextBase
.