我正在尝试为我们的注销方法编写一个单元测试。除其他外,它 FormsAuthentication.SignOut(). 。然而,它抛出一个 System.NullReferenceException.

我创建了一个模拟; HttpContext (使用起订量),但它显然缺少一些东西。

我的模拟上下文包含:

  • 一个嘲笑 HttpRequestBaseRequest
  • 一个嘲笑 HttpResponseBaseResponse
  • 与一个 HttpCookieCollectionRequest.Cookies 另一个关于 Response.Cookies
  • 一个嘲笑 IPrincipalUser

我知道我可以走包装路线并注入一个空的 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* 的东西。您可以使用 伊沃娜 并直接测试您的方法,而无需模拟所有这些依赖项并获得神秘的异常。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top