Chapter 17. Testability

The problem with troubleshooting is that trouble shoots back.

As developers, we often find ourselves in situations where we spend a significant amount of time trying to troubleshoot issues that might occur in our Web API implementation. In many cases, we just use a trial-and-error method with a browser or an HTTP debugger, but this sort of manual testing is time-consuming, irreproducible, and error-prone. To make things worse, as the number of scenarios that our Web API can cover increases, manually testing every possible path becomes a daunting task.

Over the years, different tools and practices have emerged to improve our lives as developers. Automated testing, for example, is one of the areas in which a lot of improvement has been made. Automated testing in this context consists of creating or configuring a piece of software to perform testing for us. There are some obvious advantages with this approach: a test becomes reproducible, and it can be run at any time, or we can even schedule it to run automatically with no interaction whatsoever.

As part of this chapter, we will explore the two most popular choices for developers to automate software testing in ASP.NET Web API: unit testing and integration testing. For those who are not familiar with these concepts, we have included a brief introduction to the subject, which also mentions test-driven development (TDD) as a core practice. Since this could be a very extensive subject, we have constrained it to ASP.NET Web API and how you can leverage it for testing all the components you build with the framework.

Unit Tests

A unit test is code that we typically write for verifying the expected behavior of some other code in isolation. We start the process of writing unit tests by splitting the application code into discrete parts, such as class methods, that are easy to manage and can be tested in isolation without affecting one another. For example, in the context of ASP.NET Web API, you might want to write different unit tests for every public method exposed in an ApiController implementation. “In isolation” means that there is no dependency between tests, so the order in which they run should not alter the final result. In fact, you should be able to run all the unit tests simultaneously.

A unit test is typically organized into three parts:

  1. Arrange: set one or more objects in a known state.
  2. Act: manipulate the state of those objects by calling some methods on it, for example.
  3. Assert: check the results of the tests and compare them against an expected outcome.

As with any piece of source code, unit tests should also be treated as a development artifact that can be stored in a source repository. By having the tests in a repository, you can use them for multiple purposes such as documenting the expected behavior of certain code or ensuring that behavior is still correct as different changes in the implementation are made. They should also be easy and fast to run; otherwise, developers would be less likely to run them.

Unit Testing Frameworks

Unit testing frameworks help simplify the process of writing unit tests by enforcing certain aspects of the test structure and also providing tools to run them. As with any framework, they are not something strictly necessary, but they can certainly speed up the process of getting the unit tests done and working.

The most common unit testing frameworks used nowadays by developers are typically part of the xUnit family, including the Visual Studio unit testing tool (which is included in the paid version of Visual Studio), or xUnit.Net, a open source initiative that we will use in this chapter to cover both integration testing and unit testing. Most of the frameworks in the xUnit family are either a direct port of JUnit or use some of its concepts or ideas, which were initially originated and became popular in extreme programming.

Getting Started with Unit Testing in Visual Studio

To make things easier for developers, the ASP.NET team has included unit testing support in the New Project dialog in Visual Studio for ASP.NET Web API applications, as shown in Figure 17-1.

New Project dialog
Figure 17-1. New Project dialog

By selecting the Create Unit Test project checkbox, you are instructing the Visual Studio project wizard to include a new Unit Test project using the testing framework of your preference (the Visual Studio Unit Testing tool is the only one available by default). When you select the Visual Studio Unit Testing tool, Visual Studio will also generate a project with a set of unit tests for the ASP.NET Web API controllers included in the default template. For other testing tools, this behavior will change based on the project template definition registered in Visual Studio for that tool.

In the case of the ASP.NET Web API project template, which includes a ValuesController, you will also find the unit test counterpart in the testing project, ValuesControllerTest. The code in Example 17-1 illustrates the Get method generated by Visual Studio in that class.

Example 17-1. Unit test generated by the ASP.NET Web API project template
[TestClass]
public class ValuesControllerTest
{
  [TestMethod]
  public void Get()
  {
    // Arrange
    ValuesController controller = new ValuesController(); // <1>

    // Act
    IEnumerable<string> result = controller.Get(); // <2>

    // Assert // <3>
    Assert.IsNotNull(result);
    Assert.AreEqual(2, result.Count());
    Assert.AreEqual("value1", result.ElementAt(0));
    Assert.AreEqual("value2", result.ElementAt(1));
  }
}

As you can see, this method is organized into Arrange, Act, and Assert parts, as we previously discussed. In the Arrange part <1>, the ValuesController under test is instantiated, follow by the Get method invocation as part of the Act <2> and the expected values assertions in the Assert part <3>.

The Assert class, as well as the TestClass and TestMethod attributes used by this unit test, are part of the Visual Studio unit testing framework. You will typically find these three with different names but similar functionality in any framework from the xUnit family.

While Example 17-1 shows how to unit test a particular controller, you will also find yourself writing unit tests for the rest of the components, such as the ones encapsulating data access or business logic as well as others that make sense only in the context of a Web API like message handlers.

xUnit.NET

xUnit.NET is another alternative in the xUnit family that began as an open source initiative from Brad Wilson and James Newkirk, also one of the authors of NUnit, the first port of JUnit in the .NET world. This framework was conceived with the goal of applying many of the best practices and lessons learned from previous experiences in unit testing and better aligning with the recent changes in the .NET platform. For example, this framework provides a nice way to check exceptions in a test compared to the other traditional frameworks. While most frameworks handle this scenario with the use of attributes, xUnit.NET uses delegates, as shown in Example 17-2.

Example 17-2. A unit test for checking an expected exception
[TestClass]
public class ValuesControllerTest
{
  [TestMethod]
  public void Get()
  {
    // Arrange
    ValuesController controller = new ValuesController();

    // Assert // <1>
    controller.Throws<HttpException>(() => controller.Get("bad")) // <2>
  }
}

As you can see in assert_throws, the Throws method provides a simple way to express the Assert <1> and Act <2> sections in a single line, where the delegate being passed to this method is responsible for throwing the exception.

Unit test organization

As a rule of thumb, a unit test should test only a single functionality or behavior; otherwise, it would be very complicated to get real feedback about what specifically was wrong. In addition, the unit test by itself wouldn’t provide any value because it would be hard to determine what the expected behavior should be. For that reason, unit tests are typically organized in methods that provide fine-grained feedback. Each method should demonstrate only one expected behavior, but it might do so by using one or more assertions. In the case of xUnit.net, these methods must be decorated with a Fact attribute to identify them as unit tests. You might also want to organize unit tests in groups using different criteria, such as unit tests for a specific component or for a specific use case. xUnit just uses classes for grouping all the tests together in what is typically called a test suite in unit testing jargon.

The Assert class

Most xUnit frameworks, including xUnit.net, use an Assert class with common static methods for doing comparisons or checking results using a fluent interface, which helps to express intent explicitly. For example, if you express that the returning value of a method call should not be null, Assert.IsNotNull(result) probably communicates that intent better than Assert.IsTrue(result == null), but this is just a personal preference of the developer writing the tests. All the methods in this Assert class throw exceptions when the condition evaluates to false, making it possible to detect whether a unit test failed.

The Role of Unit Testing in Test-Driven Development

Test-driven development (TDD) is a design technique in which you use unit tests to drive the design of production code by writing the tests first, followed by the necessary application code to make the tests pass. When TDD is applied correctly, the resulting artifacts are the application code along with the unit tests describing the expected behavior, which you can also use at any time later to make sure the behavior of the production code is still correct. However, TDD is focused not only on using unit tests to reduce the number of bugs in the production code, but also in improving that code’s design. Since you are writing unit tests first, you are describing how the production code should behave before it is actually written. You are writing the application code that you need, and only that, with no chance of writing any unnecessary implementation.

A common mistake is to assume that writing some unit tests implies TDD. While TDD requires unit tests for driving the design of your code, the opposite is not necessarily true. You can write unit tests after the code is written, which is typically done for increasing the percentage of code that you have covered with tests, but that does not mean you are using TDD, as your application code already exists.

The red and green cycle

When you are writing unit tests, the words red and green can be used as a replacement for failure and success, respectively. These are the colors most test runners also use to help developers quickly identify which tests are passing or failing. TDD also makes extensive use of these two colors for driving the development of new functionality. Because you write a test for code that does not exist yet, your first test run will fail. You then write code designed to pass the test and rerun the test. You will get a green light if the behavior of the production code is correct, a red one if your production code still needs some work. In that way, you are constantly moving through a red/green cycle. However, once a test passes, you should stop writing new production code until you have a new test, with new requirements, that fails. You basically write enough application code to make the test pass, which might not be perfect at first glance, but you have a tool to verify that any improvement made to the production code does not affect the expected behavior. If you want to improve or optimize certain aspects of the production code, you can do it as long as you don’t modify the public interface of your components. The tests can still be used as backup to make sure the underlying behavior is still the same.

Code refactoring

The term code refactoring refers to the act of changing the internal implementation of a component while leaving its observable behavior intact—for example, reducing a large public method to a set of individually concise and single-purpose methods, which are easier to maintain. When you are refactoring your production code, you don’t modify the existing unit tests, which would imply you are adding, removing, or modifying existing functionality. The existing unit tests are used to make sure the observable behavior of the application code is still the same after the refactoring. If you have, for example, an action in a controller that returns a list of customers and a unit test for verifying that behavior, that test will only expect to receive the same list of customers no matter how the internal implementation is getting that list. If you detect some inconsistencies in that code, you can use refactoring to improve it and use the existing unit tests to make sure nothing was broken with the introduced changes.

Example 17-3 shows some code that can be improved through internal refactoring.

Example 17-3. Two methods instantiating an HttpClient instance
public abstract class IssueSource : IIssueSource
{
  HttpMessageHandler _handler = null;

  protected IssueSource(HttpMessageHandler handler = null)
  {
    _handler = handler;
  }

  public virtual Task<IEnumerable<Issue>> FindAsync()
  {
    HttpClient client;

    if (_handler != null)
      client = new HttpClient(_handler);
    else
      client = new HttpClient();

    // Do something with the HttpClient instance ...
  }

  public virtual Task<IEnumerable<Issue>> FindAsyncQuery(dynamic values)
  {
    HttpClient client;

    if (_handler != null)
      client = new HttpClient(_handler);
    else
      client = new HttpClient();

    // Do something with the HttpClient instance ...
  }
}

Both methods are initializing a new HttpClient instance. If that code requires some changes, such as the addition of new settings, it has to be changed in the two methods. That code can be moved to a common method, which is kept internal to the implementation, as shown in Example 17-4.

Example 17-4. HttpClient initialization moved to a common method
public abstract class IssueSource : IIssueSource
{
  HttpMessageHandler _handler = null;

  protected IssueSource(HttpMessageHandler handler = null)
  {
    _handler = handler;
  }

  public virtual Task<IEnumerable<Issue>> FindAsync()
  {
    HttpClient client = GetClient();

    // Do something with the HttpClient instance ...
  }

  public virtual Task<IEnumerable<Issue>> FindAsyncQuery(dynamic values)
  {
    HttpClient client = GetClient();

    // Do something with the HttpClient instance ...
  }

  protected HttpClient GetClient()
  {
    HttpClient client;

    if (_handler != null)
      client = new HttpClient(_handler);
    else
      client = new HttpClient();

    return client;
  }
}

We’ve removed the duplicated code without modifying the external interface of this class. The expected behavior is still the same, so the unit tests don’t have to be changed, and they can be used to verify this change did not break anything.

Dependency injection and mocking

Dependency injection is another practice that usually goes hand in hand with unit testing in static languages where depedencies cannot be easily replaced at runtime. Since unit testing focuses on testing the behavior of certain code in isolation, you would want to minimize the impact of any external dependency during testing. By using dependency injection, you replace all the hardcoded dependencies and inject them at runtime, so their behavior can be faked. For example, if a Web API controller relies on a data access class for querying a database, we will not want to unit test the controller with that explicit dependency. That would imply a database is initialized and ready to be used every time the test is run, which might not always be the case. The controller is first decoupled from the real data access class implementation via an interface or an abstract class, which is later injected into the controller through either an argument in the constructor or a property setter. As part of the unit test, the only pending task is to create a fake class that implements the interface or abstract class and also satisfies the requirements of the test. That fake class could simply mimic the same interface and return the expected data for the test from a memory list previously initialized in the same test. In that way, we get rid of the database dependency in the tests. There are multiple open source frameworks, such as Moq or RhinoMocks, for automatically generating a fake or mock from an interface or base class, and setting expectations for how that mock class should be behave. Example 17-5 shows a fragment of a unit test for our Issue Tracker Web API that instantiates a mock (from the Moq framework) for emulating the behavior of a data access class.

Example 17-5. A unit test that uses a mock class
public class IssuesControllerTests
{
  private Mock<IIssueSource> _mockIssueSource = new Mock<IIssueSource>(); // <1>
  private IssuesController _controller;

  public IssuesControllerTests()
  {
    _controller = new IssuesController(_mockIssueSource.Object); // <2>
  }

  [Fact]
  public void ShouldCallFindAsyncWhenGETForAllIssues()
  {
    _controller.Get();
    _mockIssueSource.Verify(i=>i.FindAsync()); // <3>
  }
}

A mock for the IIssueSource interface is instantiated via the Mock class provided by the Moq framework <1> and injected into the IssuesController constructor <2>. The unit test invokes the Get method on the controller and verifies that the FindAsync method on the Mock object was actually called <3>. Verify is a method also provided by the Moq framework for checking if a method was invoked or not, and how many times it was invoked (invoking the method FindAsync more than once would imply a bug in the code, for example). If this framework isn’t used, a lot of manual and repetitive code would be needed to implement a similar functionality.

Unit Testing an ASP.NET Web API Implementation

There are several components in an ASP.NET Web API implementation that you will want to test in isolation. As part of this chapter, we will cover some of them, such as ApiController, MediaTypeFormatter, and HttpMessageHandler. Toward the end of the chapter, we will also explore the idea of using the HttpClient class and in-memory hosting for doing integration testing.

Unit Testing an ApiController

An ApiController in the context of ASP.NET Web API acts as an entry point to your Web API implementation. It serves as a bridge for exposing application logic to the external world via HTTP. One of the main things you will want to test is how the controller reacts to different request messages in isolation. You can pick which messages to use based on the supported scenarios or use cases for the Web API. Isolation is also a key aspect for unit testing, as you cannot assume that everything is correctly set up in the Web API runtime before running the tests. For example, if your ApiController relies on the authenticated user for doing certain operations, the configuration of that user should also be done within the unit tests. The same idea applies for the initialization of the request messages.

As part of this section, we will use the ApiController that we built for managing issues as the starting point. We will try to write unit tests against that code for covering some of the supported use cases. Let’s start with Example 17-6.

Example 17-6. Our IssuesController implementation
public class IssuesController : ApiController
{
  private readonly IIssueSource _issueSource;

  public IssuesController(IIssueSource issueSource )
  {
    _issueSource = issueSource;
  }

  public async Task<Issue> Get(string id) // <1>
  {
    var issue = await _issueSource.FindAsync(id);
    if(issue == null)
      throw new HttpResponseException(HttpStatusCode.NotFound);
    return issue;
  }

  public async Task<HttpResponseMessage> Post(Issue issue) // <2>
  {
    var createdIssue = await _issueSource.CreateAsync(issue);
    var link = Url.Link("DefaultApi", new {Controller = "issues",
       id = createdIssue.Id});
    var response = Request.CreateResponse(HttpStatusCode.Created, createdIssue);
    response.Headers.Location = new Uri(link);
    return response;
  }
}

Example 17-6 shows our first implementation of the IssuesController, which does not look too complex at first glance. It contains a Get method for retrieving an existing issue <1>, and a Post method for adding a new issue <2>. It also depends on an IIssueSource instance for handling persistence concerns.

Testing the Get method

Our first Get method, shown in Example 17-7, looks very simple and returns an existing issue by delegating the call to the IIssueSource implementation. As the unit tests should not rely on a concrete IIssueSource implementation, we will use a Mock instead.

Example 17-7. Our first unit test for the Get method
public class IssuesControllerTests
{
  private Mock<IIssueSource> _mockIssueSource = new Mock<IIssueSource>();
  private IssuesController _controller;

  public IssuesControllerTests()
  {
    _controller = new IssuesController(_mockIssueSource.Object); // <1>
  }

  [Fact]
  public void ShouldReturnIssueWhenGETForExistingIssue()
  {
    var issue = new Issue();

    _mockIssueSource.Setup(i => i.FindAsync("1"))
       .Returns(Task.FromResult(issue)); // <2>

    var foundIssue = _controller.Get("1").Result; // <3>

    Assert.Equal(issue, foundIssue); // <4>
  }
}

Example 17-7 mainly checks that the controller invokes the FindAsync method in the IIssueSource instance to return an existing issue. The controller is first initialized with a mock instance of IIssueSource <1> as part of the test initialization. That mock instance is set up to return an issue when it receives an argument equal to “1” <2>, which is the same argument that gets passed to the Get method controller <3>. Finally, the issue returned by the controller is compared with the issue injected in the mock to make sure they are the same <4>.

That’s the happy path when an issue is returned by the IIssueSource implementation, but we will also want to test how the controller reacts when a requested issue is not found. We will create a new test for verifying that scenario, as shown in Example 17-8.

Example 17-8. Unit test for covering the nonexisting issue path
[Fact]
public void ShouldReturnNotFoundWhenGETForNonExistingIssue()
{
  _mockIssueSource.Setup(i => i.FindAsync("1"))
    .Returns(Task.FromResult((Issue)null)); // <1>

  var ex = Assert.Throws<AggregateException>(() =>
  {
    var task = _controller.Get("1");
    var result = task.Result;
  }); // <2>

  Assert.IsType<HttpResponseException>(ex.InnerException); // <3>
  Assert.Equal(HttpStatusCode.NotFound,
    ((HttpResponseException) ex.InnerException).Response.StatusCode); // <4>
}

The controller is throwing an HttpException with a status code equal to 404 when the issue is not found. The unit test is initializing the Mock for returning a Task with a null result when the issue ID is equal to 1. As part of the Assert section in the unit test, we are using the Throws method (provided in xUnit.NET) in the Assert class for checking whether a method returns an exception or not <2>. The Throws method receives a delegate that might throw an exception, and it tries to capture it. Finally, we are checking if the thrown exception is of the type HttpResponseException <3> and the status code on that exception is set to 404 <4>.

Testing the Post method

The Post method in the controller will create a new issue. We need to verify that the issue is correctly passed to the IIssuesSource implementation, and also that the response headers are correctly set before leaving the controller. Since this controller method relies on the HttpRequestMessage and UrlHelper instances in the controller context for generating a response and the link to the new resource, some tedious work is required to initialize the runtime configuration and the routing table, as shown in Example 17-9.

Example 17-9. Unit test for covering the nonexistent issue path
_controller.Configuration = new HttpConfiguration(); // <1>

var route = _controller.Configuration.Routes.MapHttpRoute(
  name: "DefaultApi",
  routeTemplate: "api/{controller}/{id}",
  defaults: new { id = RouteParameter.Optional }
); // <2>

var routeData = new HttpRouteData(route,
  new HttpRouteValueDictionary
  {
    { "controller", "Issues" }
  }
);

_controller.Request = new HttpRequestMessage(HttpMethod.Post,
   "http://test.com/issues");  // <3>
_controller.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey,
controller.Configuration);
_controller.Request.Properties.Add(HttpPropertyKeys.HttpRouteDataKey,
   routeData); // <4>

A new HttpConfiguration object is instantiated and set in the controller instance <1>. The route is set up in <2> and added to the existing configuration object. A new request object is created and set up with the HTTP verb and URI expected by the test <3>. Finally, the routing data and configuration object are associated with the request object through the generic property bag Properties, which is the one used by UrlHelper to look up these objects <4>.

The ASP.NET Web API team already simplified this scenario in Web API, as we will see in the next section. In the meantime, if you are still using the first Web API release, there is a set of extension methods provided as part of the WebApiContrib project, which configures the controller with a single line of code (see Example 17-10).

Example 17-10. Extension methods for configuring the ApiController in a test
public static class ApiControllerExtensions
{
  public static void ConfigureForTesting(this ApiController controller,
    HttpRequestMessage request,
    string routeName = null,
    IHttpRoute route = null);

  public static void ConfigureForTesting(this ApiController controller,
    HttpMethod method,
    string uri,
    string routeName = null,
    IHttpRoute route = null);
}

These extension methods receive the request instance or HTTP method to use, and optionally a URL or default route to use in the test. We will be using these extension methods in the rest of the chapter for simplifying the code in the tests. Our first unit test is shown in Example 17-11.

Example 17-11. First test to verify that the CreateAsync method was invoked
[Fact]
public void ShouldCallCreateAsyncWhenPOSTForNewIssue()
{
  //Arrange
  _controller.ConfigureForTesting(HttpMethod.Post, "http://test.com/issues"); // <1>
  var issue = new Issue();
  _mockIssueSource.Setup(i => i.CreateAsync(issue))
    .Returns(() => Task.FromResult(issue)); // <2>

  //Act
  var response = _controller.Post(issue).Result; // <3>

  //Assert
  _mockIssueSource.Verify(i=>i
    .CreateAsync(It.Is<Issue>(iss => iss.Equals(issue)))); // <4>
}

The HttpRequestMessage and the UrlHelper instances in the IssuesController are initialized with the extension method ConfigureForTesting instances in the instances in the <1>. Once the controller is initialized, the IIssueSource mock instance is set to return an asynchronous task, which emulates the work of persisting the issue in the backend <2>. The Post method in the controller is invoked with a new issue <3>. The test verifies that the CreateAsync method in the mock instance was actually invoked <4>.

An additional test is needed to verify that a valid response is returned after we invoke the CreateAsync method in the IIssueSource implementation Example 17-12.

Example 17-12. Second test to verify the response message
[Fact]
public void ShouldSetResponseHeadersWhenPOSTForNewIssue()
{
  //Arrange
  _controller.ConfigureForTesting(HttpMethod.Post, "http://test.com/issues");
  var createdIssue = new Issue();
  createdIssue.Id = "1";
  _mockIssueSource.Setup(i => i.CreateAsync(createdIssue)).Returns(() =>
  Task.FromResult(createdIssue)); // <1>

  //Act
  var response = _controller.Post(createdIssue).Result; // <2>

  //Assert
  response.StatusCode.ShouldEqual(HttpStatusCode.Created); // <3>
  response.Headers.Location.AbsoluteUri.ShouldEqual("http://test.com/issues/1");
}

The IIssueSource mock instance is set up to return a task with the created issue <1>. The created issue is passed as an argument to the controller instance <2>. The test verifies that the expected HTTP status code is equal to Created and the new resource location is http://test.com/issues/1. At this point, you should have a pretty good idea of what is involved in unit testing a controller in ASP.NET Web API. In the next sections, we will discuss what needs to be done for testing a MediaTypeFormatter and an HttpMessageHandler <3>.

IHttpActionResult in Web API 2

Web API 2 introduces a new interface IHttpActionResult (equivalent to ActionResult in ASP.NET MVC) that greatly simplifies the unit testing story for controllers. A controller method can now return an implementation of IHttpActionResult, which internally uses the Request or the UrlHelper for link generation, so the unit test cares only about the returned IHttpActionResult instance. The following code shows the equivalent version of the Post method using an instance of IHttpActionResult:

public async Task<IHttpActionResult> Post(Issue issue)
{
  var createdIssue = await _issueSource.CreateAsync(issue);
  var result = new CreatedAtRouteNegotiatedContentResult<Issue>(
    "DefaultApi",
    new Dictionary<string, object> { { "id", createdIssue.Id } },
      createdIssue,
      this);

  return result;
}

CreatedAtRouteNegotiatedContentResult is an implementation also included in the framework for handling this scenario. A new resource is created and the location is set in the response message. The unit test is much simpler too, as illustrated in Example 17-13.

Example 17-13. Second test to verify the response message
[Fact]
public void ShouldSetResponseHeadersWhenPOSTForNewIssue()
{
  //Arrange
  var createdIssue = new Issue();
  createdIssue.Id = "1";
  _mockIssueSource.Setup(i => i.CreateAsync(createdIssue)).Returns(() =>
  Task.FromResult(createdIssue));

  //Act
  var result = _controller.Post(createdIssue).Result as
  CreatedAtRouteNegotiatedContentResult; // <1>

  //Assert
  result.ShouldNotBeNull(); // <2>
  result.Content.ShouldBeType<Issue>(); // <3>
}

The unit test just casts the returned result to the expected type, which is CreateAtRouteNegotiatedContentResult in this test <1>, and verifies that the result is not null <2> and the content set in that result is an instance of the Issue type <3>. No previous initialization code was required, as all the content negotiation and link management logic is now encapsulated in the IHttpActionResult implementation, which is a concern this unit test does not care about.

Unit Testing a MediaTypeFormatter

As a central piece for handling new media types or content negotiation, a MediaTypeFormatter implementation involves several aspects you’ll want to address during unit testing. Those aspects include correct handling of the supported media types, converting a model from or to a given media type, or optionally checking the correct configuration of some settings such as encoding or mappings.

We can get a rough idea of what unit testing a MediaTypeFormatter implementation involves by looking at its class definition in Example 17-14.

Example 17-14. MediaTypeFormatter class definition
public abstract class MediaTypeFormatter
{
  public Collection<Encoding> SupportedEncodings { get; }

  public Collection<MediaTypeHeaderValue> SupportedMediaTypes { get; }

  public Collection<MediaTypeMapping> MediaTypeMappings { get; }

  public abstract bool CanReadType(Type type);

  public abstract bool CanWriteType(Type type);

  public virtual Task<object> ReadFromStreamAsync(Type type, Stream readStream,
    HttpContent content, IFormatterLogger formatterLogger);

  public virtual Task WriteToStreamAsync(Type type, object value,
    Stream writeStream, HttpContent content, TransportContext transportContext);
}

The following conditions can be checked with different unit tests:

  • The supported media types (see Example 17-15) were correctly configured in the SupportedMediaTypes collection. In the example we built in Chapter 13 for supporting syndication media types such as Atom or RSS, this test would imply that the collection contains application/atom+xml and application/rss+xml for supporting those media types, respectively.
Example 17-15. Unit tests for checking the supported media types
[Fact]
public void ShouldSupportAtom()
{
  var formatter = new SyndicationMediaTypeFormatter();

  Assert.True(formatter.SupportedMediaTypes
    .Any(s => s.MediaType == "application/atom+xml"));
}

[Fact]
public void ShouldSupportRss()
{
  var formatter = new SyndicationMediaTypeFormatter();

  Assert.True(formatter.SupportedMediaTypes
    .Any(s => s.MediaType == "application/rss+xml"));
}
  • The implementation supports serializing or deserialization of a given model type in the CanReadType and CanWriteType methods (see Example 17-16).
Example 17-16. Unit tests for checking whether an implementation can read or write a type
[Fact]
public void ShouldNotReadAnyType()
{
  var formatter = new SyndicationMediaTypeFormatter();

  var canRead = formatter.CanReadType(typeof(object));

  Assert.False(canRead);
}

[Fact]
public void ShouldWriteAnyType()
{
  var formatter = new SyndicationMediaTypeFormatter();

  var canWrite = formatter.CanWriteType(typeof(object));

  Assert.True(canWrite);
}
  • The code for writing or reading a model into/out to stream using one of the supported media types is working correctly (see Example 17-17). That implies testing the WriteToStreamAsync and ReadFromStreamAsync methods, respectively.
Example 17-17. Unit tests for verifying the behavior of the WriteToStreamAsync method
[Fact]
public void ShouldSerializeAsAtom()
{
  var ms = new MemoryStream();

  var content = new FakeContent();
  content.Headers.ContentType = new MediaTypeHeaderValue("application/atom+xml");

  var formatter = new SyndicationMediaTypeFormatter();

  var task = formatter.WriteToStreamAsync(typeof(List<ItemToSerialize>),
    new List<ItemToSerialize> { new ItemToSerialize { ItemName = "Test" }},
    ms,
    content,
    new FakeTransport()
  );

  task.Wait();

  ms.Seek(0, SeekOrigin.Begin);

  var atomFormatter = new Atom10FeedFormatter();
  atomFormatter.ReadFrom(XmlReader.Create(ms));

  Assert.Equal(1, atomFormatter.Feed.Items.Count());
}

public class ItemToSerialize
{
  public string ItemName { get; set; }
}

public class FakeContent : HttpContent
{
  public FakeContent()
    : base()
  {
  }

  protected override Task SerializeToStreamAsync(Stream stream, TransportContext
     context)
  {
    throw new NotImplementedException();
  }

  protected override bool TryComputeLength(out long length)
  {
    throw new NotImplementedException();
  }
}

public class FakeTransport : TransportContext
{
  public override ChannelBinding GetChannelBinding(ChannelBindingKind kind)
  {
    throw new NotImplementedException();
  }
}

Example 17-17 shows a unit test that serializes a list of items of the type ItemToSerialize, also defined in the test as an Atom feed. The test mainly verifies that the SyndicationMediaTypeFormatter can serialize the list of items when the content type is equal to application/atom+xml. As the WriteToStreamAsync method expects instances of HttpContent and TransportContext, and those are not used at all in the implementation with the exception of the headers, two fake classes were defined that don’t do anything special. The test also deserializes the stream back to an Atom feed using the WCF syndication class to make sure the serialization was done properly.

  • All the required settings are correctly initialized. For example, if you have a requirement for supporting the media type mappings in the query string, a unit test could check that this mapping was correctly configured in the MediaTypeMappings collection, as Example 17-18 demonstrates.
Example 17-18. Unit test for checking the supported media type mappings
[Fact]
public void ShouldMapAtomFormatInQueryString()
{
  var formatter = new SyndicationMediaTypeFormatter();

  Assert.True(formatter.MediaTypeMappings.OfType<QueryStringMapping>()
    .Any(m => m.QueryStringParameterName == "format" &&
              m.QueryStringParameterValue == "atom" &&
              m.MediaType.MediaType == "application/atom+xml"));
}

Example 17-18 illustrates a sample of a test that checks if a MediaTypeMapping was defined as a query string argument for mapping a query string variable format with value atom to the media type application/atom+xml.

Unit Testing an HttpMessageHandler

An HttpMessageHandler is a generic interception mechanism for the Web API runtime pipeline. It’s asynchronous by nature, and typically contains a single method, SendAsync, for processing a request message (HttpRequestMessage) that returns a Task instance representing some work to obtain a response (HttpResponseMessage). See Example 17-19.

Example 17-19. HttpMessageHandler class definition
public abstract class HttpMessageHandler
{
  protected internal abstract Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken);
}

The SendAsync method cannot be directly called in a unit test, as it is not public, but the framework provides a class, System.Net.Http.MessageInvoker, that you can use for that purpose. This class receives the HttpMessageHandler instance in the constructor and provides a public method, SendAsync, for invoking the method with the same name on the handler. Example 17-20 simply illustrates how the SendAsync method in a sample HttpMessageHandler is unit tested. However, an HttpMessageHandler might receive external dependencies or contain some other public methods you will want to test as well.

Example 17-20. Unit testing an HttpMessageHandler
[Fact]
public void ShouldInvokeHandler()
{
    var handler = new SampleHttpMessageHandler();

    var invoker = new HttpMessageInvoker(handler);
    var task = invoker.SendAsync(new HttpRequestMessage(), new CancellationToken());

    task.Wait();

    var response = task.Result;

    // Assertions over the response
    // ......
}

Unit Testing an ActionFilterAttribute

Action filters are not any different from the HTTP message handlers when it comes to message interception, but they run much deeper in the runtime pipeline once the action context has been initialized and the action is about to execute. The action filter base class System.Web.Http.Filters.ActionFilterAttribute (see Example 17-21) provides two methods that can be overridden, OnActionExecuting and OnActionExecuted, for intercepting the call before and right after the action has been executed.

Example 17-21. ActionFilterAttribute class definition
public abstract class ActionFilterAttribute : FilterAttribute,
  IActionFilter,
  IFilter
{
  public virtual void OnActionExecuted(HttpActionExecutedContext
    actionExecutedContext);

  public virtual void OnActionExecuting(HttpActionContext
    actionContext);
}

Both methods are public, so they can be called directly from a unit test. Example 17-22 shows a very basic implementation of a filter for authenticating clients with an application key. The idea is to use this concrete implementation to show how the different scenarios can be unit tested.

Example 17-22. Action filter for authenticating clients with an application key
public interface IKeyVerifier
{
  bool VerifyKey(string key);
}

public class ApplicationKeyActionFilter : ActionFilterAttribute
{
  public const string KeyHeaderName = "X-AuthKey";

  IKeyVerifier keyVerifier;

  public ApplicationKeyActionFilter()
  {
  }

  public ApplicationKeyActionFilter(IKeyVerifier keyVerifier) // <1>
  {
      this.keyVerifier = keyVerifier;
  }

  public Type KeyVerifierType // <2>
  {
      get;
      set;
  }

  public override void OnActionExecuting(HttpActionContext
    actionContext)
  {
      if (this.keyVerifier == null)
      {
          if (this.KeyVerifierType == null)
          {
              throw new Exception("The keyVerifierType was not provided");
          }

          this.keyVerifier = (IKeyVerifier)Activator
            .CreateInstance(this.KeyVerifierType);
      }

      IEnumerable<string> values = null;

      if (actionContext.Request.Headers
        .TryGetValues(KeyHeaderName, out values)) // <3>
      {
          var key = values.First();

          if (!this.keyVerifier.VerifyKey(key)) // <4>
          {
              actionContext.Response =
                new HttpResponseMessage(HttpStatusCode.Unauthorized);
          }
      }
      else
      {
          actionContext.Response =
            new HttpResponseMessage(HttpStatusCode.Unauthorized);
      }

      base.OnActionExecuting(actionContext);
  }
}

This action filter receives an instance of IKeyVerifier, which is used to verify whether a key is valid <1>. Because an action filter can also be used as an attribute, the implementation provides a property, KeyVerifierType <2>, to set the IKeyVerifier in that scenario. This filter only implements the OnActionExecuting method, which runs before the action is executed. This implementation checks for an X-Auth header in the request message set in the context <3>, and tries to pass the value of that header to the IKeyVerifier instance for authentication <4>. If the key cannot be validated or it is not found in the request message, the filter sets a response message with an HTTP status code of 401 Unauthorized in the current context, and the pipeline execution is interrupted.

The first unit test, shown in Example 17-23, will test the scenario in which a valid key is passed in the request message.

Example 17-23. Unit test for a valid key
[Fact]
public class ApplicationKeyActionFilterFixture
{
  public void ShouldValidateKey()
  {
      var keyVerifier = new Mock<IKeyVerifier>();
      keyVerifier
          .Setup(k => k.VerifyKey("mykey"))
          .Returns(true); // <1>

      var request = new HttpRequestMessage();
      request.Headers.Add("X-AuthKey", "mykey"); // <2>

      var actionContext = InitializeActionContext(request); // <3>

      var filter = new ApplicationKeyActionFilter(keyVerifier.Object);
      filter.OnActionExecuting(actionContext); // <4>

      Assert.Null(actionContext.Response); // <5>
  }
}

private HttpActionContext InitializeActionContext(HttpRequestMessage request)
{
    var configuration = new HttpConfiguration();

    var route = configuration.Routes.MapHttpRoute(
      name: "DefaultApi",
      routeTemplate: "api/{controller}/{id}",
      defaults: new { id = RouteParameter.Optional }
    );

    var routeData = new HttpRouteData(route,
        new HttpRouteValueDictionary
        {
            { "controller", "Issues" }
        }
    );

    request.Properties[HttpPropertyKeys.HttpRouteDataKey] = routeData;

    var controllerContext = new HttpControllerContext(configuration, routeData, request);

    var actionContext = new HttpActionContext
    {
        ControllerContext = controllerContext
    };

    return actionContext;
}

As a first step <1>, the unit test initializes a mock or fake instance of the IKeyVerifier that will return true when the key passed to the VerifyKey method is equal to mykey. Secondly <2>, a new HTTP request message is created and the custom header X-AuthKey is set to the value expected by the IKeyVerifier instance. The Action context expected by the filter is initialized in the method InitializeActionContext <3>, which requires a lot of common boilerplate code to inject the routing configuration and the request message into the constructor of the HttpControllerContext class. Finally, the method OnActionExecuting is invoked <4> with the initialized context and an assertion is made for a null response <5>. If nothing fails in the action filter implementation, the response will never be set in the context, so the test will pass.

Example 17-24 will test the next scenario, in which a key is not valid and a response is returned with the status code 401 (Unauthorized).

Example 17-24. Unit test for an invalid key
[Fact]
public void ShouldNotValidateKey()
{
    var keyVerifier = new Mock<IKeyVerifier>();
    keyVerifier
        .Setup(k => k.VerifyKey("mykey"))
        .Returns(true);

    var request = new HttpRequestMessage();
    request.Headers.Add(ApplicationKeyActionFilter.KeyHeaderName, "badkey"); // <1>

    var actionContext = InitializeActionContext(request);

    var filter = new ApplicationKeyActionFilter(keyVerifier.Object);
    filter.OnActionExecuting(actionContext);

    Assert.NotNull(actionContext.Response); // <2>
    Assert.Equal(HttpStatusCode.Unauthorized,
       actionContext.Response.StatusCode); // <3>
}

The main difference with the previous test is that the application set in the request message <1> is different from the one expected by the IKeyVerifier mock instance. After the OnActionExecuting method is invoked, two assertions are made to make sure the response set in the context is not null <2> and its status code is equal to 401 (Unauthorized) <3>.

Unit Testing Routes

Route configuration is another aspect that you might want to cover with unit testing. Although it’s not a component itself, a complex route configuration that does not follow the common conventions might lead to some problems that you will want to figure out sooner rather than later, and far before the implementation is deployed. The bad news is that ASP.NET Web API does not offer any support for unit testing routes out-of-the-box, so custom code is required. That custom code will basically use some of the built-in Web API infrastructure components, like the DefaultHttpControllerSelector and ApiControllerActionSelector, to infer the controller type and action name for a given HttpRequestMessage and routing configuration. See Example 17-25.

Example 17-25. A generic method for testing routes
public static class RouteTester
{
  public static void TestRoutes(HttpConfiguration configuration,
    HttpRequestMessage request,
    Action<Type, string> callback)
  {
    var routeData = configuration.Routes.GetRouteData(request);
    request.Properties[HttpPropertyKeys.HttpRouteDataKey] = routeData;

    var controllerSelector = new DefaultHttpControllerSelector(configuration); // <1>
    var controllerContext = new HttpControllerContext(configuration, routeData,
       request);

    controllerContext.ControllerDescriptor = controllerSelector
      .SelectController(request); // <2>

    var actionSelector = new ApiControllerActionSelector(); // <3>

    var action = actionSelector.SelectAction(controllerContext).ActionName; // <4>
    var controllerType = controllerContext.ControllerDescriptor
       .ControllerType; // <5>

    callback(controllerType, action); // <5>
  }
}

Example 17-25 illustrates a generic method that receives an instance of HttpConfiguration with the routing configuration and an HttpRequestMessage, and invokes a callback with the selected controller type and action name. This method first instantiates a DefaultHttpControllerSelector class using the HttpConfiguration received as an argument to determine the controller type <2>. The controller is selected afterward with the HttpRequestMessage also passed as an argument <3>. Once the controller is selected, an ApiControllerActionSelector is instantiated next to infer the action name <4>. The action name and controller type are obtained in <5> and <6>. Finally, a callback is called with the inferred controller type and action name. This callback will be used by the unit test to perform the assertions. See Example 17-26.

Example 17-26. A unit test that uses the RouteTester implementation
[Fact]
public void ShouldRouteToIssueGET()
{
  var config = new HttpConfiguration();
  config.Routes.MapHttpRoute(name: "Default",
    routeTemplate: "api/{controller}/{id}"); // <1>

  var request = new HttpRequestMessage(HttpMethod.Get,
  "http://www.example.com/api/Issues/1"); // <2>

  RouteTester.TestRoutes(config, request, // <3>
    (controllerType, action) =>
    {
      Assert.Equal(typeof(IssuesController), controllerType);
      Assert.Equal("Get", action);
    });
}

Example 17-26 illustrates how the RouteTester class can be used in a unit test to verify a route configuration. An HttpConfiguration is initialized and configured with the routes to test <1>, and also an HttpRequestMessage with the HTTP verb and URL to invoke <2>. As the final step, the RouteTester is used with the configuration and request instances to determine the controller type and action name. As part of the callback, the test defines the assertions for comparing the inferred controller type and action name with the expected ones <3>.

Integration Tests in ASP.NET Web API

Thus far we have discussed unit testing, which focuses on testing components in isolation, but what happens if you would like to test how all your components collaborate in a given scenario? This is where you will find integration testing very useful. In the case of a Web API, integration testing focuses more on testing a complete call end to end from the client to the service, including all the components in the stack such as controllers, filters, message handlers, or any other component configured in your Web API runtime. For example, you might want to use an integration test to enable basic authentication with an HttpMessageHandler and verify how that handler behaves with your existing controllers from the point of a view of a client application. Ideally, you will also unit test those components to make sure they behave correctly in isolation.

For doing integration testing in ASP.NET Web API, we will use HttpClient, which can handle requests in an in-memory hosted server. This has some evident advantages for simplifying the tests, as there is no need to open ports or send messages across the network. As shown in Example 17-27, the HttpClient class contains several constructors that receive an HttpMessageHandler instance. As covered in Chapter 4, the HttpServer class is an HttpMessageHandler implementation, which means it can be directly injected in an HttpClient instance to automatically handle any message sent by the client in a test.

Example 17-27. HttpClient constructors
public class HttpClient : HttpMessageInvoker
{
   public HttpClient();
   public HttpClient(HttpMessageHandler handler);
   public HttpClient(HttpMessageHandler handler, bool disposeHandler);
}

We can configure an HttpServer instance with the HttpMessageHandler from our previous implementation and use it with an HttpClient instance within an integration test to verify how the scenario works end to end. See Example 17-28.

Example 17-28. Integration tests for basic authentication
public class BasicAuthenticationIntegrationTests
{
  [Fact]
  public ShouldReturn404IfCredentialsNotSpecified()
  {
      var config = new HttpConfiguration();
      config.Routes.MapHttpRoute(name: "Default",
        routeTemplate: "api/{controller}/{action}/{id}",
        defaults: new { id = RouteParameter.Optional }); // <1>

      config.MessageHandlers.Add(new BasicAuthHttpMessageHandler()); // <2>

      var server = new HttpServer(config);

      var client = new HttpClient(server); // <3>

      var task = client.GetAsync("http://test.com/issues"); // <4>
      task.Wait();

      var response = task.Result;

      Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode); // <5>
  }
}

As shown in Example 17-28, we can still use a unit testing framework for automating the integration tests. Our test is configuring a server in-memory with a default route <1> and a BasicAuthHttpMessageHandler <2>, which internally implements basic authentication. That server is injected in the HttpClient <3>, so the call to http://test.com/issues with GetAsync will be routed to that server <4>. In the case of this test, no authorization header was set in the HttpClient, so the expected behavior is that the BasicAuthHttpHandler returns a response message with status code 404 (Unauthorized) <5>. Authentication is just a scenario where integration testing makes sense, but as you can imagine, this idea can be extended to any scenario that requires coordination between multiple components.

Conclusion

TDD can be used as a very effective tool to drive the design and implementation of your Web API. As a side effect, you will get unit tests reflecting the expected behavior of the implementation that you can also use to make progressive enhancements in the existing code. Those tests can be used to make sure that nothing broke with the introduction of new changes and that the implementation still satisfies the expected behavior. There are two commonly used practices with TDD: dependency injection and code refactoring. While the former focuses on generating more testable code by removing explicit dependencies, the latter is used to improve the quality of the existing code. In addition to unit testing, which focuses on testing specific pieces of code in isolation, you can also use integration testing for testing a scenario end to end, and see how the different components in the implementation interact.

Get Designing Evolvable Web APIs with ASP.NET now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.