Web API Deep Dive - Testing with EF Rollbacks across HTTP (part 6 of 6)

Microsoft’s ASP.Net Web API 2.2 allows you to easily create REST style APIs on an IIS website. Microsoft has some great documentation on how to get started with it, so I won’t rehash that here. Instead, I’m going to go a little deeper into some powerful features that can be used with Web API.

Overview

A few weeks ago I wrote an article called Cleaning Up EF 6 Tests With Transactions Rollbacks, where I showed how to create integration tests that set up some data in a database, run a test against the data, and then roll back all changes to the data. The rollback was possible because all of the changes to the data were wrapped up inside a transaction.

This posts extends that idea, but instead of a test calling methods on a repository or service layer, this test makes an HTTP call against a Web API endpoint, while preserving the ability to revert all changes to the database as part of a transaction rollback.

The interesting part here is that we will create a database context, start a transaction against that context, create some test data, and then spin up a Web API server that uses that same context. When we’re done with our tests, we’ll roll back the transaction so that the database changes are all reverted.

2016-01-27 Update - clarified when Configuration is available in an API Controller

Base API Test Class

First, let’s look at the TransactionTest class that we created earlier. If you haven’t seen this before, I’d recommend reviewing my earlier post.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class TransactionTest
{
protected EntitiesV3 context;
protected DbContextTransaction transaction;

[AssemblyInitialize]
public static void AssemblyStart(TestContext testContext)
{

RetryDbConfiguration.SuspendExecutionStrategy = true;
}

[TestInitialize]
public void TransactionTestStart()
{

context = new EntitiesV3();
transaction = context.Database.BeginTransaction();
}

[TestCleanup]
public void TransactionTestEnd()
{

transaction.Rollback();
transaction.Dispose();
context.Dispose();
}

[AssemblyCleanup]
public static void AssemblyEnd()
{

RetryDbConfiguration.SuspendExecutionStrategy = false;
}
}

We’ve seen that before. It allows us to create a test inside a database transaction that can be rolled back. How do we extend this to Web API calls? Here is the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
[TestClass]
public abstract class WebApiHostedTests : TransactionTest
{
private static HttpServer server;
private string baseurl = "http://localhost:50366/";
private HttpClient client;
private HttpRequestMessage request;
private HttpResponseMessage response;

[TestInitialize]
public void WebApiHostedTestBaseStart()
{

var config = new HttpConfiguration();
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
config.Properties["context"] = context;
WebApiConfig.Register(config);
server = new HttpServer(config);
client = new HttpClient(server);
}

[TestCleanup]
public void WebApiHostedTestBaseEnd()
{

if (request != null)
{
request.Dispose();
request = null;
}
if (response != null)
{
response.Dispose();
response = null;
}
}

protected TestHttpResponse<T> Get<T>(string url)
{
request = createRequest<string>(url, "application/json",
HttpMethod.Get, null, new JsonMediaTypeFormatter());
response = client.SendAsync(request).Result;
var data = (T)((ObjectContent)(response.Content)).Value;
return new TestHttpResponse<T>(response, data);
}

private HttpRequestMessage createRequest<T>(string url, string mthv,
HttpMethod method, T content, MediaTypeFormatter formatter)
where T : class
{
var request = new HttpRequestMessage();
request.RequestUri = new Uri(baseurl + url);
request.Headers.Accept.Add(
new MediaTypeWithQualityHeaderValue(mthv));
request.Method = method;
request.Content = new ObjectContent<T>(content, formatter);

// Add authentication here if needed

return request;
}
}

Let’s look at this one piece at a time. First, we have the WebApiHostedTestBaseStart. Because of the [TestStart] attribute, this will be run before each test is run. Because this class inherits from the TransactionTest class, the TransactionTestStart method has already run, so we have a context ready. The WebApiHostedTestBaseStart does the following:

  • It creates an HttpConfiguration and adds the context as a property on this configuration. (the Properties property is a collection of arbitrary objects that we can see inside our Web API code. This allows us to pass the context into our Web Api project. We’ll look at that momentarily)
  • It creates an in-memory HttpServer, which allows us to run our Web API project in the test runner process without having to spin up IIS or any other web server.
  • It creates an HttpClient and configures it to connect to our in-memory HttpServer.

The WebApiHostedTestBaseEnd method runs after each test is run because of the [TestCleanup] attribute. This cleans up the objects that we created in WebApiHostedTestBaseStart.

The Get method, given a URL, makes a HTTP GET call to the server, receives JSON back from the server, and deserializes the JSON into a strongly typed object of type T. The deserialization happens in this line:

1
var data = (T)((ObjectContent)(response.Content)).Value;

TestHttpResponse

The Get method then returns a TestHttpResponse, which is an object that contains the strongly typed T along with the raw HTTP response. This allows our test to check the data that was returned, and it allows our test to check the HTTP response codes and headers that are returned. A Web API is composed of many HTTP endpoints that should return various HTTP responses codes, and this allows us to test those response codes.

Here is the implementation of TestHttpResponse:

1
2
3
4
5
6
7
8
9
10
11
public class TestHttpResponse<T>
{
public TestHttpResponse(HttpResponseMessage message, T data)
{
this.Message = message;
this.Data = data;
}

public HttpResponseMessage Message { get; private set; }
public T Data { get; private set; }
}

Context Injection

Next, we’ll make use of the context that this sets up for us. In our API controller, we’ll need to grab the context from our HttpConfiguration. HttpConfiguration is a property on all controllers that inherit from ApiController. Our code above created and then injected the context into the configuration with this line:

1
config.Properties["context"] = context;

And this is how we can make use of that context in our API Controllers:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private FooContext _context;
private FooContext context
{
get
{
if (_context == null)
{
if (Configuration == null || Configuration.Properties == null
|| !Configuration.Properties.ContainsKey("context"))
{
// No context was passed in from our tests
// Create our context using the connection
// string "FooContext"
context = new FooContext();
}
else
{
// This is running inside an integration test, so use
// the context that is provided by the test:
context = (FooContext)Configuration.Properties["context"];
}
}
return _context;
}
}

2016-01-27 Update - The Configuration property cannot be used in the constructor for your API Controller, because it is not populated until after the controller is constructed. If you try to use Configuration in the constructor, it will be null. Instead, it can be accessed on-the-fly by creating a property, such as seen above, that will only be used after the object is created.

Writing a Test

Now that we have all of these great things set up, how do we use them? We need to create a test class that inherits from WebApiHostedTests:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[TestClass]
public class FooControllerTests : WebApiHostedTests
{
[TestMethod]
public void CallingGetItemByNameReturnsItem()
{

// Arrange
context.Items.Add(new Item() {Name = "Thing", Id = 17});
context.SaveChanges();

// Act
var url = string.Format("api/items/{0}", 17);
var response = Get<Item>(url);

// Assert
response.Message.StatusCode.Should().Be(HttpStatusCode.OK);
response.Data.Id.Should().Be(17);
response.Data.Name.Should().Be("Thing");
}
}

The goal of the code in this post is to make these tests simple and readable. The test adds data to your context, makes an HTTP call, specifying what type of data it expects to get back (Item in this case), and it then verifies that the HTTP Response looks good and that the data that is returned looks good. The test itself has minimal boilerplate code, and it cleans up after itself.

Closing

Thanks for sticking with me in this series on Web API. Feel free to tweet me @codethug if you’d like to follow up on anything.