How to make ASP.net session state testable

In the initial version, the wrapper simply throws an exception if HttpContext.Current or HttpContext.Current.Session is not available.

So in our Test-Methods we have to “mock” out the SessionState somehow. Basically we just need to override the Session State with a static dictionary within the computing Thread.

I would like to write my tests like this…

[Test]
public void TestOverride()
{
  using(SessionVariableStorage.Override(new Dictionary<string, object>()))
  {
    // normally a call to code that uses session variables internally
    var sessionString = new SessionVariable("sessionString"); 
    Assert.IsFalse(sessionString.HasValue);
    sessionString.Value = "Hello, world!";
    Assert.IsTrue(sessionString.HasValue);
    Assert.AreEqual("Hello, world!", sessionString.Value);
  }
}

To enable that, I need an extra static class “SessionVariableStorage” and some modifications in the SessionVariable<T>.

public static class SessionVariableStorage
{
  internal static readonly ThreadVariable<IDictionary<string, object>> Storage =
    new ThreadVariable<IDictionary<string, object>>();

  public static IDisposable Override(IDictionary<string, object> storage)
  {
    return Storage.Use(storage);
  }
}

Every time before I access the HttpSession I have to check wether it’s currently overridden or not. Here is the new version (requiring ThreadVariable<T>)

/// <summary>
/// Wrapper class for <see cref="HttpContext.Session"/>.
/// Author: Lars Corneliussen
/// Source: https://startbigthinksmall.wordpress.com/2008/05/14/how-to-wrap-the-aspnet-session-state/
/// </summary>
/// <typeparam key="T">
/// The type of the value to be stored.
/// </typeparam>
public class SessionVariable<T>
{
  private readonly string key;
  private readonly Func<T> initializer;

  /// <summary>
  /// Initializes a new session variable.
  /// </summary>
  /// <param name="key">
  /// The key to use for storing the value in the session.
  /// </param>
  public SessionVariable(string key)
  {
    if (string.IsNullOrEmpty(key))
      throw new ArgumentNullException("key");

    this.key = GetType() + key;
  }

  /// <summary>
  /// Initializes a new session variable with a initializer.
  /// </summary>
  /// <param name="key">
  /// The key to use for storing the value in the session.
  /// </param>
  /// <param name="initializer">
  /// A function that is called in order to create a 
  /// default value per session.
  /// </param>
  public SessionVariable(string key, Func<T> initializer) : this(key)
  {
    if (initializer == null) 
      throw new ArgumentNullException("initializer");

    this.initializer = initializer;
  }


  private object GetInternalValue(bool initializeIfNessesary)
  {
    if (SessionVariableStorage.Storage.HasCurrent)
    {
      var dictionary = SessionVariableStorage.Storage.Current;

      object value;

      if (!dictionary.TryGetValue(key, out value) && initializeIfNessesary
          && initializer != null)
        dictionary.Add(key, value = initializer());

      return value;
    }
    else
    {
      HttpSessionState session = CurrentSession;

      var value = session[key];

      if (value == null && initializeIfNessesary
          && initializer != null)
        session[key] = value = initializer();

      return value;
    }
  }

  private static HttpSessionState CurrentSession
  {
    get
    {
      var current = HttpContext.Current;

      if (current == null)
        throw new InvalidOperationException(
          "No HttpContext is not available.");

      var session = current.Session;
      if (session == null)
        throw new InvalidOperationException(
          "No Session available on current HttpContext.");
      return session;
    }
  }

  /// <summary>
  /// Indicates wether there is a value present or not.
  /// </summary>
  public bool HasValue
  {
    get { return GetInternalValue(false) != null; }
  }

  /// <summary>
  /// Sets or gets the value in the current session.
  /// </summary>
  /// <exception cref="InvalidOperationException">
  /// If you try to get a value while none is set. 
  /// Use <see cref="ValueOrDefault"/> for safe access.
  /// </exception>
  public T Value
  {
    get
    {
      object v = GetInternalValue(true);

      if (v == null)
        throw new InvalidOperationException(
          "The session does not contain any value for '"
          + key + "'.");

      return (T) v;
    }
    set
    {
      if (SessionVariableStorage.Storage.HasCurrent)
        SessionVariableStorage.Storage.Current.Add(key, value);
      else
        CurrentSession[key] = value;
    }
  }

  /// <summary>
  /// Gets the value in the current session or if 
  /// none is available <c>default(T)</c>.
  /// </summary>
  public T ValueOrDefault
  {
    get
    {
      object v = GetInternalValue(true);

      if (v == null)
        return default(T);

      return (T)v;
    }
  }

  /// <summary>
  /// Clears the value in the current session.
  /// </summary>
  public void Clear()
  {
    if (SessionVariableStorage.Storage.HasCurrent)
      SessionVariableStorage.Storage.Current.Remove(key);
    else
      CurrentSession.Remove(key);
  }
}

One thought on “How to make ASP.net session state testable

  1. Pingback: How to wrap the ASP.net session state « .Net Braindrops

Comments are closed.