views:

405

answers:

3

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

I've created myself 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."

+3  A: 

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();
    }
Vadim
You can follow example I showed you in modified answer how you can break your external dependencies.
Vadim
+1 as I mentioned in my answer, the wrapper is the way to go.
eglasius
+2  A: 

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.

eglasius
Thanks for the response. I've removed my comments suggesting about the "big application" as I have re-worded my question since. Obviously, regardless of application size, creating a wrapper and using dependency injection for such classes/methods is *the* way to go. (And will be the way I got if I want this unit test to pass)I would however, still like to leave my question as it is.Cheers for the tip on the Reflector, I will look into that.
jeef3
A: 

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 :)

womp
Perfect, exactly the answer I was after. Cheers :)
jeef3