Testing fun with ASP.NET MVC

So I assumed that much like MonoRail, ASP.NET MVC would have created, at the very least, abstractions on top of HttpContext.  Although HttpContext is an absolute beast of a god class, creating an abstraction is possible.  Let’s look at MonoRail’s approach:

public interface IRailsEngineContext : IServiceContainer, IServiceProvider
{
    // Methods
    T GetService<T>();
    void Transfer(string path, bool preserveForm);

    // Properties
    string ApplicationPath { get; }
    string ApplicationPhysicalPath { get; }
    ICacheProvider Cache { get; }
    IServiceProvider Container { get; }
    Controller CurrentController { get; set; }
    IPrincipal CurrentUser { get; set; }
    Flash Flash { get; }
    IDictionary Items { get; }
    Exception LastException { get; set; }
    NameValueCollection Params { get; }
    IRequest Request { get; }
    string RequestType { get; }
    IResponse Response { get; }
    IServerUtility Server { get; }
    IDictionary Session { get; }
    ITrace Trace { get; }
    HttpContext UnderlyingContext { get; }
    string Url { get; }
    UrlInfo UrlInfo { get; }
    string UrlReferrer { get; }
}

Things are looking okay, most objects are hidden behind an interface.  Those interfaces that have members that expose other classes, are interfaces as well.  Only the items important to a Controller action are present, and the original HttpContext is still present.  But ideally, you can go off of the interfaces, IResponse, IRequest, etc.  This makes testing MonoRail items (controllers, filters, etc.) straightforward.

Now, let’s take a look at ASP.NET MVC:

public abstract class HttpContextBase : IServiceProvider
{
    // Methods
    protected HttpContextBase();
    public virtual void AddError(Exception errorInfo);
    public virtual void ClearError();
    public virtual object GetGlobalResourceObject(string classKey, string resourceKey);
    public virtual object GetGlobalResourceObject(string classKey, string resourceKey, CultureInfo culture);
    public virtual object GetLocalResourceObject(string virtualPath, string resourceKey);
    public virtual object GetLocalResourceObject(string virtualPath, string resourceKey, CultureInfo culture);
    public virtual object GetSection(string sectionName);
    public virtual object GetService(Type serviceType);
    public virtual void RewritePath(string path);
    public virtual void RewritePath(string path, bool rebaseClientPath);
    public virtual void RewritePath(string filePath, string pathInfo, string queryString);
    public virtual void RewritePath(string filePath, string pathInfo, string queryString, bool setClientFilePath);

    // Properties
    public virtual Exception[] AllErrors { get; }
    public virtual HttpApplicationStateBase Application { get; }
    public virtual HttpApplication ApplicationInstance { get; set; }
    public virtual Cache Cache { get; }
    public virtual IHttpHandler CurrentHandler { get; }
    public virtual RequestNotification CurrentNotification { get; }
    public virtual Exception Error { get; }
    public virtual IHttpHandler Handler { get; set; }
    public virtual bool IsCustomErrorEnabled { get; }
    public virtual bool IsDebuggingEnabled { get; }
    public virtual bool IsPostNotification { get; }
    public virtual IDictionary Items { get; }
    public virtual IHttpHandler PreviousHandler { get; }
    public virtual ProfileBase Profile { get; }
    public virtual HttpRequestBase Request { get; }
    public virtual HttpResponseBase Response { get; }
    public virtual HttpServerUtilityBase Server { get; }
    public virtual HttpSessionStateBase Session { get; }
    public virtual bool SkipAuthorization { get; set; }
    public virtual DateTime Timestamp { get; }
    public virtual TraceContext Trace { get; }
    public virtual IPrincipal User { get; set; }
}

Still very similar, but this time it’s an abstract class.  But a very strange abstract class – it has no abstract members!  All members are virtual.  Well that was nice of them, providing a default implementation that can be overridden for testing.  Ah, but what’s the default implementation?  “throw new NotImplementedException()”.  For every. single. member.  Brilliant!

Next, let’s try to actually test using this class.  Abstract base classes minefield base classes (touch it and it asplodes!) are exposed for the various items we might care about, including Request, Response, Session, Server, Trace, Application.  Wait, go back one.  Trace exposes TraceContext.

Which is not abstract.

Which is sealed.

Kaboom!

I still like creating application-specific wrappers on all of these items.  But when you have to create things like filters, it can be painful to work around these limitations.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in ASP.NET MVC, MonoRail. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://colinjack.lostechies.com Colin Jack

    Didn’t realize thats how HttpContectBase behaves, smelllly.

    I personally feel like some of the ASP.NET MVC design has been very poor. Lots of unbelievably heavyweight abstract classes being flung about the place and little thought about ease of testing.

    Worst example I’ve seen so far (in preview 5) was that your IModelBinder takes a ControllerContext which is a ridiculously heavyweight class thats difficult to mock. Plus in reality the first thing you’re going to want to do is access the Form collection which involves navigating through a very long chain of objects (whilst I don’t always follow LOD it certainly needed to be applied better here).

  • http://webgambit.com Karthik

    This issue was well discussed when they first changed HttpContext into an Abstract base class instead of an Interface.

    It was originally wrote it to be an IHttpContext but was changed in later previews to be an abstract base class.

    This was because someone decided that making it an interface would “break too many people’s code” if they modified the interface definition in further releases of the framework. Because your code is supposed to compile on the first try when you upgrade your framework, right? :P

  • http://www.lostechies.com/members/bogardj/default.aspx bogardj

    @Karthik

    Yeah, such a weird argument…and “throw new NotImplementedExceptoin” is an improvement?

  • http://www.lostechies.com/members/mbratton/default.aspx mbratton

    I always wondered why this wasn’t an interface. Now I wish I didn’t know.