Channeling John Malkovich

One of the frustrations of working with the .NET Framework is the framework engineers’ practice of favoring encapsulation over testability. System.Net.WebRequest and its cohorts are a great example. On the one hand, these classes are a good example of a well-encapsulated design; using a factory pattern for instantiation; and branching off a number of useful subclasses from the base, such as System.Net.HttpWebRequest and System.Net.FtpWebRequest. The WebRequest family implements connection management, authorization, SSL connections, an optional asynchronous programming model, and a variety of other features. Aside from a few quirks and edge cases, it’s a fine class.

Except when it comes to testing.

I will not be mocked

The essential problem is that there isn’t any way to just create a WebRequest and pass it to your object. The classic way to isolate dependencies would be use a Dependency Injection pattern to pass an interface to the target object so you could construct it with a real instance or some sort of fake for testing purposes. But construction is done through a factory, so that option’s off the table, at least at some level – at the point you create a WebRequest, you have to do it through WebRequest.Create

A bit of research reveals that there’s a way to get into the factory, however: WebRequest.RegisterPrefix allows one to register a factory class to create WebRequests for a given protocol spec, e.g.

// urls beginning with mock:// will be StubWebRequests
WebRequest.RegisterPrefix("mock", new StubWebRequest.StubWebRequestCreate());
WebRequest req = WebRequest.Create("mock://something"); 
// req is StubWebRequest

So, from there it’s a SMOP to implement a factory that creates a StubWebRequest:

public class StubWebRequest : WebRequest
{
 public class StubWebRequestCreate : IWebRequestCreate
 {
    public WebRequest Create(Uri uri)
    {
      return new StubWebRequest(uri);
    }
 }
 public override string Method { get; set; }
 public override string ContentType { get; set; }
 // etc... Implement other members here
}

This works well, as far as getting your fake WebRequest created and injected into your code. But what if you want more of a Mock object, that acts like a WebRequest, to the point of responding to GetResponse() / BeginGetResponse(AsyncCallback,object). Then the story takes on a few twists.

— Gordon Weakliem

---

Comment

Commenting is closed for this article.