[Posted by Pavel Repin]
If you find yourself writing simple HTTP handler code that produces and consumes structured data (for instance, some RESTful application), you may wonder how to test it without fiddling with IIS or configuration files. Here's a trick to write pure unit tests that verify your IHttpHandler implementation does what you expect. By "pure unit tests", I mean test code that:
- works without configuration files (like web.config);
- needs no servers (like Cassini, IIS, or your own mock HTTP server with full ASP.NET pipeline that you were about to write and debug just before you stumbled upon this article);
- doesn't access file system (like ashx files);
- avoids globals (like
HttpContext.Current).
The Unit Tests In Action
Pretend we have an IHttpHandler implementation, TagHandler, that lets browsers retrieve a list of tags (using GET requests) and post new tags (using POST requests). The handler responds to GET requests with an array of tag objects serialized as JSON. It also parses submitted data (JSON) in POST requests and creates tags based on that.
Here are the unit tests that verify the POST behavior. Note the use of a helper class, HttpHandlerTest, that does all the tricky bits such as setting up HttpContext instance, creating a fake request body, the session state, and more.
[TestFixture]
public class TagHandlerTestFixture {
private Dictionary<String, Tag> tags;
[SetUp] public void SetUp() {
// the tags object is always empty at the start of each test case!
tags = new Dictionary<String, Tag>();
}
[Test, ExpectedException(typeof (ArgumentException))]
public void PostTagWithInvalidRequestContentType() {
HttpHandlerTest http = HttpHandlerTest.ImitatePost(
"tags.ashx", "{}", Encoding.UTF8);
// Won't like text/xml, it wants application/json!
http.Context.Request.ContentType = "text/xml";
http.Execute(new TagHandler(tags));
}
[Test] public void PostTag() {
HttpHandlerTest http = HttpHandlerTest.ImitatePost("tags.ashx",
"{\"name\":\"foo\",\"description\":\"a a a\"}", Encoding.UTF8);
http.Context.Request.ContentType = "application/json";
http.Execute(new TagHandler(tags));
Assert.IsTrue(tags.ContainsKey("foo"));
Assert.AreEqual("a a a", tags["foo"].Description);
}
// ... more tests
}
You start off with setting up either a fake GET or POST request with HttpHandlerTest.Imitate[Get/Post]() method, then you set up additional preconditions in the initialized HttpContext object accessible as a property on HttpHandlerTest instance. Then you pass the IHttpHandler instance to the Execute() method. Your handler does its work. And then you can verify your post-conditions using NUnit assertions.
Here are some more tests from the same fixture that exercise the GET behavior:
[Test] public void GetTagsResponseIsApplicationJson() {
HttpHandlerTest http = HttpHandlerTest.ImitateGet("tags.ashx");
http.Execute(new TagHandler(tags));
Assert.AreEqual("application/json",
http.Context.Response.ContentType);
}
[Test] public void GetNoTags() {
HttpHandlerTest http = HttpHandlerTest.ImitateGet("tags.ashx");
http.Execute(new TagHandler(tags));
Assert.AreEqual("{}", http.Output);
Assert.AreEqual(Encoding.UTF8.GetType(),
http.Context.Response.ContentEncoding.GetType());
}
HttpHandlerTest Internals
Under the hood, HttpHandlerTest creates an instance of HttpContext initialized with a custom version of SimpleWorkerRequest. ImitateGet gives you an instance of HttpHandlerTest geared for testing GET requests, which do not have any content in the request body. ImitatePost is for simulating POST requests that do have content in the request. It should be easy to add support for other HTTP methods like DELETE, HEAD, and PUT.
public class HttpHandlerTest
{
// Methods
protected HttpHandlerTest(string requestMethod, string requestBody,
Encoding requestEncoding, string page, string query);
public virtual void Execute(IHttpHandler testSubject);
public static HttpHandlerTest ImitateGet(string virtualPath);
public static HttpHandlerTest ImitatePost(string virtualPath,
string requestBody, Encoding requestEncoding);
// Properties
public virtual HttpContext Context { get; }
public virtual string Output { get; }
}
Links
-
Full source code for
HttpHandlerTestwith the example web app. Note: the example web app usesAjaxPro.JSON.2.dllandnunit.framework.dllwhich are bundled in the "lib" directory. You can get everything by:svn co http://codebackpack.googlecode.com/svn/tags/how-to-test-httphandlers - Of course once I checked that nobody else found solution just as easy as the one presented here, and wrote this post, I was immediately shown ("Thank you" GeorgeR) that there was already a post about this by Phil Haack and my solution looks very similar :( Oh well, at least this proves that this is not such a bad idea after all.
-
Lutz Roeder's Reflector has proven very valuable in figuring out which methods of
SimpleWorkerRequestto override.





Comments