Posts
203
Comments
1116
Trackbacks
51
Use a Fake Http Channel to Unit Test with HttpClient

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.

posted on Wednesday, February 16, 2011 12:26 AM Print
Comments
Gravatar
# re: Use a Fake Http Channel to Unit Test with HttpClient
Rocky
3/7/2011 1:55 AM
Can you please explain how you would change GetContentStream to use XmlSerializer instead of DataContractSerializer?
Gravatar
# re: Use a Fake Http Channel to Unit Test with HttpClient
Steve
3/7/2011 6:40 AM
@Rocky - In terms of the sample above, it's as simple as changing the line of code that refers to the DataContractSerializer to use the XmlSerializer instead. Even more interesting is that the WCF Web APIs allow you to change the serialziation you use on the server. The default on the server is to use the DataContractSerializer but you can implement your Processor (side note: Processors are being renamed to "handlers" in the latest drop) to use an XmlSerializer instead. Similarly, if you don't like the JSON serializer they use, can you implement your own handler to use the JSON.NET library instead for example.
Gravatar
# re: Use a Fake Http Channel to Unit Test with HttpClient
Rocky
3/7/2011 9:04 AM
Thanks @Steve, but I seem to have a problem with the type being passed in from this line: httpClient.Channel = new FakeHttpChannel<PastedXSDAsTypeClass>
Here is where I don't know how to assign the type like you had done for var serializer = new DataContractSerializer(typeof(T));

Here is my first attempt to convert use XmlSerializer:
var serializer = new XmlSerializer(typeof(T)); //
Stream stream = new MemoryStream();
serializer.Deserialize(stream); // there is no overload for the type and this causing problems this.responseObject
stream.Position = 0;
return stream;
Gravatar
# re: Use a Fake Http Channel to Unit Test with HttpClient
Rocky
3/7/2011 9:17 AM
My mistake I was asking to deserialize instead of: serializer.Serialize(stream, this.responseObject);
@Steve this is an awesome demo you put together and it has taught me so much about structuremap and beyond. Thanks!

Post Comment

Title *
Name *
Email
Comment *  
 

View Steve Michelotti's profile on LinkedIn

profile for Steve Michelotti at Stack Overflow, Q&A for professional and enthusiast programmers




Google My Blog

Tag Cloud