ASP.NET OOP and Unit Testing
page 3 of 7
by Brian Mains
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 38912/ 94

Session/ Cache Objects

I mentioned before the complications with some of the ASP.NET collections objects, like Request, Response, Cache, Session, and ViewState. These objects are specialized and to customize the actual implementations can be complicated and can take some work.  How can we expose this?  Let us look at the Session collection. Session is a specialized collection and to use the Session at a class level is available through the Page object as well as the HttpContext.Current property.

However, in a test environment, only a dictionary of string keys and object values is needed, and because it is the test environment, the test does not have as many problems as the web environment. Since the test exists outside of any page lifecycle and will not be recycled at the unloading of the page, the unit test does not need to worry about serializing and storing the values.  This is both good and bad.

But, what if abstraction was added? What if a custom class representing both the ASP.NET caching mechanism and a testing mechanism can be used instead?  For instance, below could be a session state manager interface.

Listing 2

public interface ISessionStateManager
{
  int Count
  {
    get;
  }
  object this[string key]
  {
    get;
    set;
  }
  string UniqueKey
  {
    get;
  }
  void Add(string key, object value);
  void Clear();
  bool Contains(string key);
  void Remove(string key);
  void RemoveAt(int index);
}

Any class implementing this interface could use a custom session state management, using any mechanism available.  For instance, one class can utilize the ASP.NET session manager and one could implement a test version using an internal dictionary that stores key/value pairs.

Remember that I mentioned the Session could be accessed through HttpContext.Current.  The ASPNetSessionManager implements the session interface as below.  It uses the Session object as the sole point of contact with the actual session object, which will be shown later.

Listing 3

public class AspNetSessionStateManager: ISessionStateManager
{
  public int Count
  {
    get
    {
      return Session.Count;
    }
  }
  public object this[string key]
  {
    get
    {
      return Session[key];
    }
    set
    {
      Session[key] = value;
    }
  }
 
  public string UniqueKey
  {
    get
    {
      return Session.SessionID;
    }
  }
 
  public void Add(string key, object value)
  {
    Session.Add(key, value);
  }
 
  public void Clear()
  {
    Session.Clear();
  }
 
  public bool Contains(string key)
  {
    return (Session["key"] != null);
  }
 
  public void Remove(string key)
  {
    Session.Remove(key);
  }
 
  public void RemoveAt(int index)
  {
    Session.RemoveAt(index);
  }
}

The Session property is private and local to this instance (not exposed by the ISessionStateManager interface) and returns an instance of the HttpSessionState, returning the ASP.NET session object when in the ASP.NET environment.

Listing 4

private System.Web.SessionState.HttpSessionState Session
{
  get
  {
    if (HttpContext.Current == null)
      throw new Exception("The context is currently null");
    return HttpContext.Current.Session;
  }
}

For testing purposes, I create a stub that stores the session keys in an internal dictionary. Any custom page class using my session interface, instead of the session object directly (which is still available) can be safely tested, as long as the correct session manager is configured (mentioned below). That is safe for unit testing and is only meant for unit testing because the stub does not serialize and store the session values across ASP.NET postbacks.

Listing 5

public class SessionStateManagerStub: ISessionStateManager
{
  private Dictionary < stringobject > _session = new Dictionary < string,
    object > ();
 
 
  public int Count
  {
    get
    {
      return _session.Count;
    }
  }
 
  public object this[string key]
  {
    get
    {
      return _session[key];
    }
    set
    {
      _session[key] = value;
    }
  }
 
  public string UniqueKey
  {
    get
    {
      return "1234567890";
    }
  }
 
  public void Add(string key, object value)
  {
    _session.Add(key, value);
  }
 
  public void Clear()
  {
    _session.Clear();
  }
 
  public bool Contains(string key)
  {
    return _session.ContainsKey(key);
  }
 
  public void Remove(string key)
  {
    _session.Remove(key);
  }
 
  public void RemoveAt(int index)
  {
    int i = 0;
    if (index < 0 || index >= _session.Count)
      throw new ArgumentOutOfRangeException("index");
 
    foreach (KeyValuePair < stringobject > pair in _session)
    {
      if (index == i++)
      {
        this.Remove(pair.Key);
        return ;
      }
    }
 
    throw new KeyNotFoundException("The index was not found");
  }
}

The problem with using this test version is that the ASP.NET version never gets tested correctly, at least with unit testing alone. There are other tools available, such as Plasma or WATIN that can unit test ASP.NET pages by accessing it (using HTTP) and parsing the HTML.

Both implementations work great, but how can you switch between the two?  This is where the configuration capabilities come into play.  ASP.NET provides the ability to create custom configuration sections, as well as specify the provider type as a string property within the custom section. Because the provider type is a string, the type can be dynamically created through the Type.GetType() method.

How does this dynamic session manager get exposed? Through a SessionStateManagement static object, similar to how the provider pattern works. It exposes all of the methods defined in ISessionStateManager, plus provides additional properties. For instance, one of the properties is the Enabled property defined in the configuration section, as shown below.

Listing 6

public static bool Enabled
{
  get
  {
    if (SessionStateSettingsSection.Instance == null)
      return false;
    else
      return SessionStateSettingsSection.Instance.Enabled;
  }
}

If disabled, an exception will be thrown disallowing any class to make use of the session capabilities. SessionStateSettingsSection also defines the session provider type, which will be used in the call to GetManager(). To expose the manager selected, the Manager property handles this.

Listing 7

private static ISessionStateManager Manager
{
  get
  {
    if (_manager == null)
    {
      if (SessionStateSettingsSection.Instance == null)
      throw new ConfigurationErrorsException(@
        "Session statesettings section has not been initialized ");if
        (!SessionStateSettingsSection.Instance.Enabled)throw new Exception(
        "Session state is not enabled");
 
      _manager = SessionStateSettingsSection.Instance.GetManager();
    }
 
    return _manager;
  }
}

All of the methods directly call the Manager's methods.

Listing 8

public static void Add(string key, object value)
{
  Manager.Add(key, value);
}
public static void Remove(string key)
{
  Manager.Remove(key);
}

The Manager property, in turn, calls the default provider setup in the configuration section.

Listing 9

<sessionStateSettings type="Nucleo.Web.Session.SessionStateManagerStub, 
   Nucleo.TestingTools" />

And, the SessionStateManagerStub methods get called, which work with the internal dictionary to add/remove items.

Listing 10

public void Add(string key, object value)
{
  _session.Add(key, value);
}
public void Remove(string key)
{
  _session.Remove(key);
}

A few things to note, because the SessionStateManagement static class is being utilized, it requires that a configuration section be used and enabled is set to true.  If anything uses SessionStateManagement without a provider being specified in the configuration file, an exception is thrown (as you may have seen above). The dynamically referenced provider is returned otherwise.

I have created an almost identical mechanism for caching; the caching mechanism uses an ICachingManager interface, as well as a CacheManagement static object. It has an ASP.NET implementation that exposes the HttpContext.Current.Cache property, as well as a stubbed cache provider that uses an internal dictionary. But with caching, there is another custom implementation that can be used outside of the ASP.NET caching mechanism and a stubbed implementation: the Enterprise Library Caching block.  Below is an internal property that gets an instance of the EL caching manager.

Listing 11

internal CacheManager Manager
{
  get
  {
    if (_manager == null)
      _manager = CacheFactory.GetCacheManager();
    return _manager;
  }
}

The cache manager takes a key for a non-default caching manager implementation, so this implementation requires a default cache manager setup, which can pose a challenge.  Outside of this, everything works the same.


View Entire Article

User Comments

Title: jjjjjk   
Name: gggg
Date: 2012-10-13 3:17:31 PM
Comment:
gggg
Title: RE: Unit Test Bangs   
Name: Brian Mains
Date: 2010-04-18 10:49:40 AM
Comment:
Unit testing for simple applications can surely be done; unit tests only help strengthen an application, but unit tests add time, so for a small project, manual unit testing by the user probably is all that is necessary.

The real question is: is the application going to grow? If it will eventually become a big app, unit testing may be favorable to grow with that application.

But IMHO no you don't need unit testing for a small application, unless you want to to strengthen the application.
Title: Unit Test Bangs   
Name: Chennaite
Date: 2010-04-13 8:00:19 AM
Comment:
Nice Article. but still need to know whether we need unit testing for simple applications?..

Hats off for brain.
Title: Good Article's   
Name: LoveIndonesia
Date: 2009-10-28 5:59:38 AM
Comment:
good articles but sometime i can't understand the mean in article because my english is bad.:D
Title: wow. just what i needed.   
Name: d potter
Date: 2009-07-22 1:06:37 PM
Comment:
this article explained just what i needed to know at just the right level of detail. spot on, man.

protip: if you're stubbing both Request and Response, combine them into an IRequestResponseManager so your stub can share the same HttpCookieCollection across simulated redirects/etc.
Title: Timely info   
Name: Scheffler
Date: 2008-08-26 6:45:50 PM
Comment:
Nice article Brian. I had a similar need today and this fit the bill quite nicely. I used a pared down version of the ISessionStateManager interface you defined above because I was only barely interacting with the Session state collection. However, this has allowed me to nicely stub out and test locally my persistence logic for some prototyping work before fully fleshing out my NHibernate logic.

Thanks for taking the time to write and share your idea.
Title: Good   
Name: venkat
Date: 2008-01-25 2:43:08 AM
Comment:
its Good...! better to provide code for this.
Title: good   
Name: max
Date: 2007-10-09 3:07:04 AM
Comment:
this article is very..good...keep it up






Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-03-29 10:40:34 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search