Use a Fake Http Channel to Unit Test with HttpClient
- by Steve Michelotti
Applications get data from lots of different sources. The most common is to get data from a database or a web service. Typically, we encapsulate calls to a database in a Repository object and we create some sort of IRepository interface as an abstraction to decouple between layers and enable easier unit testing by leveraging faking and mocking. This works great for database interaction. However, when consuming a RESTful web service, this is is not always the best approach. The WCF Web APIs that are available on CodePlex (current drop is Preview 3) provide a variety of features to make building HTTP REST services more robust. When you download the latest bits, you’ll also find a new HttpClient which has been updated for .NET 4.0 as compared to the one that shipped for 3.5 in the original REST Starter Kit. The HttpClient currently provides the best API for consuming REST services on the .NET platform and the WCF Web APIs provide a number of extension methods which extend HttpClient and make it even easier to use. Let’s say you have a client application that is consuming an HTTP service – this could be Silverlight, WPF, or any UI technology but for my example I’ll use an MVC application: 1: using System;
2: using System.Net.Http;
3: using System.Web.Mvc;
4: using FakeChannelExample.Models;
5: using Microsoft.Runtime.Serialization;
6:
7: namespace FakeChannelExample.Controllers
8: {
9: public class HomeController : Controller
10: {
11: private readonly HttpClient httpClient;
12:
13: public HomeController(HttpClient httpClient)
14: {
15: this.httpClient = httpClient;
16: }
17:
18: public ActionResult Index()
19: {
20: var response = httpClient.Get("Person(1)");
21: var person = response.Content.ReadAsDataContract<Person>();
22:
23: this.ViewBag.Message = person.FirstName + " " + person.LastName;
24:
25: return View();
26: }
27: }
28: }
On line #20 of the code above you can see I’m performing an HTTP GET request to a Person resource exposed by an HTTP service. On line #21, I use the ReadAsDataContract() extension method provided by the WCF Web APIs to serialize to a Person object. In this example, the HttpClient is being passed into the constructor by MVC’s dependency resolver – in this case, I’m using StructureMap as an IoC and my StructureMap initialization code looks like this:
1: using StructureMap;
2: using System.Net.Http;
3:
4: namespace FakeChannelExample
5: {
6: public static class IoC
7: {
8: public static IContainer Initialize()
9: {
10: ObjectFactory.Initialize(x =>
11: {
12: x.For<HttpClient>().Use(() => new HttpClient("http://localhost:31614/"));
13: });
14: return ObjectFactory.Container;
15: }
16: }
17: }
My controller code currently depends on a concrete instance of the HttpClient. Now I *could* create some sort of interface and wrap the HttpClient in this interface and use that object inside my controller instead – however, there are a few why reasons that is not desirable:
For one thing, the API provided by the HttpClient provides nice features for dealing with HTTP services. I don’t really *want* these to look like C# RPC method calls – when HTTP services have REST features, I may want to inspect HTTP response headers and hypermedia contained within the message so that I can make intelligent decisions as to what to do next in my workflow (although I don’t happen to be doing these things in my example above) – this type of workflow is common in hypermedia REST scenarios. If I just encapsulate HttpClient behind some IRepository interface and make it look like a C# RPC method call, it will become difficult to take advantage of these types of things.
Second, it could get pretty mind-numbing to have to create interfaces all over the place just to wrap the HttpClient. Then you’re probably going to have to hard-code HTTP knowledge into your code to formulate requests rather than just “following the links” that the hypermedia in a message might provide.
Third, at first glance it might appear that we need to create an interface to facilitate unit testing, but actually it’s unnecessary. Even though the code above is dependent on a concrete type, it’s actually very easy to fake the data in a unit test. The HttpClient provides a Channel property (of type HttpMessageChannel) which allows you to create a fake message channel which can be leveraged in unit testing. In this case, what I want is to be able to write a unit test that just returns fake data. I also want this to be as re-usable as possible for my unit testing. I want to be able to write a unit test that looks like this:
1: [TestClass]
2: public class HomeControllerTest
3: {
4: [TestMethod]
5: public void Index()
6: {
7: // Arrange
8: var httpClient = new HttpClient("http://foo.com");
9: httpClient.Channel = new FakeHttpChannel<Person>(new Person { FirstName = "Joe", LastName = "Blow" });
10:
11: HomeController controller = new HomeController(httpClient);
12:
13: // Act
14: ViewResult result = controller.Index() as ViewResult;
15:
16: // Assert
17: Assert.AreEqual("Joe Blow", result.ViewBag.Message);
18: }
19: }
Notice on line #9, I’m setting the Channel property of the HttpClient to be a fake channel. I’m also specifying the fake object that I want to be in the response on my “fake” Http request. I don’t need to rely on any mocking frameworks to do this. All I need is my FakeHttpChannel. The code to do this is not complex:
1: using System;
2: using System.IO;
3: using System.Net.Http;
4: using System.Runtime.Serialization;
5: using System.Threading;
6: using FakeChannelExample.Models;
7:
8: namespace FakeChannelExample.Tests
9: {
10: public class FakeHttpChannel<T> : HttpClientChannel
11: {
12: private T responseObject;
13:
14: public FakeHttpChannel(T responseObject)
15: {
16: this.responseObject = responseObject;
17: }
18:
19: protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)
20: {
21: return new HttpResponseMessage()
22: {
23: RequestMessage = request,
24: Content = new StreamContent(this.GetContentStream())
25: };
26: }
27:
28: private Stream GetContentStream()
29: {
30: var serializer = new DataContractSerializer(typeof(T));
31: Stream stream = new MemoryStream();
32: serializer.WriteObject(stream, this.responseObject);
33: stream.Position = 0;
34: return stream;
35: }
36: }
37: }
The HttpClientChannel provides a Send() method which you can override to return any HttpResponseMessage that you want. You can see I’m using the DataContractSerializer to serialize the object and write it to a stream. That’s all you need to do.
In the example above, the only thing I’ve chosen to do is to provide a way to return different response objects. But there are many more features you could add to your own re-usable FakeHttpChannel. For example, you might want to provide the ability to add HTTP headers to the message. You might want to use a different serializer other than the DataContractSerializer. You might want to provide custom hypermedia in the response as well as just an object or set HTTP response codes. This list goes on.
This is the just one example of the really cool features being added to the next version of WCF to enable various HTTP scenarios. The code sample for this post can be downloaded here.