HttpContext 中需要什么才能允许 FormsAuthentication.SignOut() 执行?
-
07-07-2019 - |
题
我正在尝试为我们的注销方法编写一个单元测试。除其他外,它 FormsAuthentication.SignOut()
. 。然而,它抛出一个 System.NullReferenceException
.
我创建了一个模拟; HttpContext
(使用起订量),但它显然缺少一些东西。
我的模拟上下文包含:
- 一个嘲笑
HttpRequestBase
在Request
- 一个嘲笑
HttpResponseBase
在Response
- 与一个
HttpCookieCollection
在Request.Cookies
另一个关于Response.Cookies
- 一个嘲笑
IPrincipal
在User
我知道我可以走包装路线并注入一个空的 FormsAuth
包装器对象在它的位置,但我真的很想避免仅仅为了修复一行代码而增加 3 个附加文件。我仍然对答案感到好奇
所以我的问题是“中需要什么 HttpContext
允许 FormsAuthentication.SignOut() to execute.
"
解决方案
这是注销的代码。
public static void SignOut()
{
Initialize();
HttpContext current = HttpContext.Current;
bool flag = current.CookielessHelper.DoesCookieValueExistInOriginal('F');
current.CookielessHelper.SetCookieValue('F', null);
if (!CookielessHelperClass.UseCookieless(current, false, CookieMode) || current.Request.Browser.Cookies)
{
string str = string.Empty;
if (current.Request.Browser["supportsEmptyStringInCookieValue"] == "false")
{
str = "NoCookie";
}
HttpCookie cookie = new HttpCookie(FormsCookieName, str);
cookie.HttpOnly = true;
cookie.Path = _FormsCookiePath;
cookie.Expires = new DateTime(0x7cf, 10, 12);
cookie.Secure = _RequireSSL;
if (_CookieDomain != null)
{
cookie.Domain = _CookieDomain;
}
current.Response.Cookies.RemoveCookie(FormsCookieName);
current.Response.Cookies.Add(cookie);
}
if (flag)
{
current.Response.Redirect(GetLoginPage(null), false);
}
}
看起来您需要一个 CookielessHelperClass 实例。太糟糕了,它是内部的并且是密封的 - 除非您使用 TypeMock,否则无法模拟它。+1 包装建议:)
其他提示
在这种情况下,NullReferenceException 实际上是由调用引发的:
current.Request.Browser["supportsEmptyStringInCookieValue"]
您可以通过调用以下命令来测试此断言:
HttpContext.Current.Request.Browser.SupportsEmptyStringInCookieValue
...这也将返回 NullReferenceException。与接受的答案相反,如果您尝试致电:
CookielessHelperClass.UseCookieless(current, false, CookieMode)
...从立即窗口中,这将毫无错误地返回。
您可以像这样修复异常:
HttpContext.Current.Request.Browser = new HttpBrowserCapabilities() { Capabilities = new Dictionary<string, string> { { "supportsEmptyStringInCookieValue", "false" } } };
...以及 FormsAuthentication.SignOut()
现在调用将会成功。
您始终可以将 FormsAuthentication.SignOut() 包装到另一个方法中并存根/模拟它。
创建 IFormsAuthenticationWrap 接口。
public interface IFormsAuthenticationWrap
{
void SignOut();
}
创建实现 IFormsAuthenticationWrap 的包装类
public class FormsAuthenticationWrap : IFormsAuthenticationWrap
{
public void SignOut()
{
FormsAuthentication.SignOut();
}
}
您的调用类将如下所示:
public class LogOutClass
{
private readonly IFormsAuthenticationWrap _formsAuthentication;
public LogOutClass() : this (new FormsAuthenticationWrap())
{
}
public LogOutClass(IFormsAuthenticationWrap formsAuthentication)
{
_formsAuthentication = formsAuthentication;
}
public void LogOutMethod()
{
// Code before SignOut
_formsAuthentication.SignOut();
// Code after SignOut
}
}
现在让我们开始测试。您可以使用 Moq 进行存根/模拟,但我将在这里展示如何手动执行此操作。创建您的存根/模拟类:
public class FormsAuthenticationStub : IFormsAuthenticationWrap
{
public void SignOut()
{
}
}
最后写测试:
[TestMethod]
public void TestLogOutMethod()
{
var logOutClass = new LogOutClass(new FormsAuthenticationStub());
logOutClass.LogOutMethod();
}
包装纸是最干净的方法。
您在评论中提到“这将是一个相当大的应用程序”,这是使用包装器的另一个论点,而不是相反。在大型应用程序中,您希望具有明确的依赖关系,并且希望轻松完成测试。
您正在交换干净的依赖项,这些依赖项可以通过模糊的依赖项轻松注入到测试中的 ASP.NET 内部工作中。
另一方面: 使用反光板. 。老实说,我不知道 ASP.NET 这个特定部分的内部依赖关系,但是您可以使用 Reflector 消除任何疑问。
不要模拟 HttpContext,在测试中使用真实的 HttpContext。这样您就不必模拟所有这些 Http* 的东西。您可以使用 伊沃娜 并直接测试您的方法,而无需模拟所有这些依赖项并获得神秘的异常。