What is needed in the HttpContext to allow FormsAuthentication.SignOut() to execute?

孤街醉人 提交于 2019-12-05 14:54:38

问题


I am trying to write a unit test for our log out method. Among other things it FormsAuthentication.SignOut(). However, it throws a System.NullReferenceException.

I've created a mock; HttpContext (using Moq), but it is obviously missing something.

My mock context contains:

  • A mocked HttpRequestBase on Request
  • A mocked HttpResponseBase on Response
  • With a HttpCookieCollection on Request.Cookies and another on Response.Cookies
  • A mocked IPrincipal on User

I am aware I could go the wrapper route and inject an empty FormsAuth wrapper object in it's place, but I would really like to avoid the 3 additional files just to fix one line of code. That and I am still curious for an answer

So my question is "What is needed in the HttpContext to allow FormsAuthentication.SignOut() to execute."


回答1:


Here's the code for signout.

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

Looks like you need a CookielessHelperClass instance. Too bad it's internal and sealed - there's no way to mock it unless you're using TypeMock. +1 for wrapper suggestions :)




回答2:


The NullReferenceException in this case is actually being thrown by the call:

current.Request.Browser["supportsEmptyStringInCookieValue"]

You can test this assertion by calling:

HttpContext.Current.Request.Browser.SupportsEmptyStringInCookieValue

...which will also return the NullReferenceException. Contrary to the accepted answer, if you attempt to call:

CookielessHelperClass.UseCookieless(current, false, CookieMode)

...from the immediate window, this will return without error.

You can fix the exception like this:

HttpContext.Current.Request.Browser = new HttpBrowserCapabilities() { Capabilities = new Dictionary<string, string> { { "supportsEmptyStringInCookieValue", "false" } } };

...and the FormsAuthentication.SignOut() call will now succeed.




回答3:


You can always wrap FormsAuthentication.SignOut() into another method and stub / mock it.

Create IFormsAuthenticationWrap interface.

public interface IFormsAuthenticationWrap
{
    void SignOut();
}

Create wrap class that implements IFormsAuthenticationWrap

public class FormsAuthenticationWrap : IFormsAuthenticationWrap
{
    public void SignOut()
    {
        FormsAuthentication.SignOut();
    }
}

Your calling class is going to look something like this:

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
    }
}

Now let's get to our test. You can stub / mock with Moq but I'm going to show here how you can do it manually. Create your stub / mock class:

public class FormsAuthenticationStub : IFormsAuthenticationWrap
{
    public void SignOut()
    {
    }
}

And the last write the test:

    [TestMethod]
    public void TestLogOutMethod()
    {
        var logOutClass = new LogOutClass(new FormsAuthenticationStub());
        logOutClass.LogOutMethod();
    }



回答4:


The wrapper is the clean way to go.

You mentioned in a comment that "this is going to be quite a big application", that's another argument to use the wrapper not the opposite. In a big application you want to have clear dependencies, and you want tests to be done easily.

You are trading clean dependencies that can be easily injected over obscure dependencies to the internal workings of asp.net in your tests.


On a different note: Use Reflector. I honestly don't know the inner dependencies of this specific part of asp.net, but you can clear any doubts with reflector.




回答5:


Don't mock HttpContext, use a real one in your tests. This way you don't have to mock all these Http* stuff. You can use Ivonna and test your method directly, without mocking all these dependencies and getting mysterious exceptions.



来源:https://stackoverflow.com/questions/1430180/what-is-needed-in-the-httpcontext-to-allow-formsauthentication-signout-to-exec

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!