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.
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.
