Practical .NET

Test-Driven Development with Content Negotiation in the Web API

There's no doubt that the ASP.NET Web API is a wonderful thing. But developing services that support content negotiation in a testable way requires a little setup.

One of the best parts of creating either Windows Communication Foundation (WCF) or Web API services is that the underlying code is just code in a class. A complete Web API method that retrieves an object through the Entity Framework (EF) and returns a corresponding Data Transfer Object (DTO) might be as simple as the code shown in Listing 1, for instance.

Listing 1: A Simple, Testable Web API Service
Public Class MyWebApiService
  Inherits ApiController

  Public Function Get(id As String) As HttpResponseMessage 
  Dim cust As Customer
  Dim custDTO As New CustomerDTO

  Using oc As New CustomerEntities
    cust = (From cust In oc.Customers
            Where cust.Id = id
            Select oc).FirstOrDefault
    If cust IsNot Nothing Then
      custDTO.Id = cust.Id
      custDTO.FirstName = cust.FirstName
      more assignment statements... 
    End If
  End Using

  Dim hrm As HttpResponseMessage 
  hrm = Me.Request.CreateResponse(
        of Customer)(HttpStatusCode.OK, cust)
  Return hrm;

  End Function
End Class

It would be convenient if I could just test my service method with code like this:

<TestMethod>
Public Sub TestGetById()
  Dim mwas As New MyWebAPIService
  Dim hrm As HttpResponseMessage 
  hrm = mwas.Get("A123")
  Assert.AreEqual(hrm.StatusCode, HttpStatusCode.OK, 
                  "Wrong status code on get by Id");
End Sub

Unfortunately, I can't.

Testing with Content Negotiation
If I had just returned a collection of JSON objects from my Web API method, I would be able to catch the result of my test and check the result. Even if I just created and returned an HttpResponseMessage object (which lets me control the return code from my method), I could probably just write the test method as I showed it.

However, either of those strategies would limit my Web API method to working only with clients that want JSON objects (which, I grant you, would be most of the clients for a Web API service). But the Web API supports content negotiation: If a client asks for an XML response, the Web API is willing to send an XML response no matter what I return from my method. In order to give the Web API the flexibility to return my result in whatever format makes sense to the client, I have to use the CreateResponse method.

And, to be fair, it's not the CreateResponse method that's the problem: it's the ASP.NET Request object that's the issue. The Visual Studio Test environment doesn't provide a Request object by default and my code won't execute without it. There is a solution: Test my code by providing a Request object on my own. In the same way that I can create an HttpResponseMessage, I can create an HttpRequestMessage object that and tie it to the Web API service with the content negotiation method I want to test.

I could generate that HttpRequestMessage in every test method. It makes sense to me, however, to create that object and tie it to my service in a TestInitialize method. Using a TestInitialize method has two benefits. First, it factors some repetitive code out of my test methods. Second, it ensures the HttpRequestMessage is created fresh before each test -- this ensures my tests really are independent of each other.

In my test class initialize method, I need to instantiate both an HttpRequestMessage class and my Web API service class, and then set the Request property on my service class to the HttpRequestMessage object:

<TestClass>
Public Class CustomerManagementServerTests
  Dim mwas As MyWebApiService

  <TestInitialize>
  public Sub SetUpCustomerManagementController()
    Dim hrqm As HttpRequestMessage 
    hrqm = new HttpRequestMessage()
    mwas = new MyWebApiService
    mwas.Request = hrqm

That's not quite enough, however: I also have to configure my HttpRequestMessage object. Fortunately, the HttpRequestMessage object is satisfied with a default HttpConfiguration object added to the HttpRequestMessage Properties collection with the key HttpPropertyKeys.HttpConfiguration. That code, at the end of my initialize method, looks like this:

HttpConfiguration cfg = new HttpConfiguration()
cmc.Request.Properties(
  HttpPropertyKeys.HttpConfigurationKey) = cfg
End Sub

Extending the Test
Now I really can use the test method that I showed before. However, in addition to testing the response code in the HttpResponseMessage, I should check that the data returned in the body of the message (my Customer object) is also correct.

To extract the content of the HttpRequestMessage object, I use the ReadAsStringAsync method on the object Content property. That gives me a Task object holding a string. Reading the Task Result property causes my code to wait for the operation to complete and then gives me the resulting string:

Dim tsk As Task(of String)
tsk = hrm.Content.ReadAsStringAsync()
Dim res As String
res = tsk.Result

To get my Customer object, I pass that string to the JsonCovert class DeserializeObject method, specifying that I want to get a Customer object back:

Dim cust as Customer
cust = JsonConvert.DeserializeObject(of Customer)(res)

Now I can check to see if I got the right result:
Assert.AreEqual("A123", cust.Id, "Wrong customer retrieved on get");

I'll grant you that having to write the code to initialize your service with the HttpRequestMessage is a pain -- but, by putting that code in a TestInitialize method, you only have to write that code once. With that out of the way, you can write all the tests you need to prove that your Web API Service works the way you think it does.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Creating Reactive Applications in .NET

    In modern applications, data is being retrieved in asynchronous, real-time streams, as traditional pull requests where the clients asks for data from the server are becoming a thing of the past.

  • AI for GitHub Collaboration? Maybe Not So Much

    No doubt GitHub Copilot has been a boon for developers, but AI might not be the best tool for collaboration, according to developers weighing in on a recent social media post from the GitHub team.

  • Visual Studio 2022 Getting VS Code 'Command Palette' Equivalent

    As any Visual Studio Code user knows, the editor's command palette is a powerful tool for getting things done quickly, without having to navigate through menus and dialogs. Now, we learn how an equivalent is coming for Microsoft's flagship Visual Studio IDE, invoked by the same familiar Ctrl+Shift+P keyboard shortcut.

  • .NET 9 Preview 3: 'I've Been Waiting 9 Years for This API!'

    Microsoft's third preview of .NET 9 sees a lot of minor tweaks and fixes with no earth-shaking new functionality, but little things can be important to individual developers.

  • Data Anomaly Detection Using a Neural Autoencoder with C#

    Dr. James McCaffrey of Microsoft Research tackles the process of examining a set of source data to find data items that are different in some way from the majority of the source items.

Subscribe on YouTube