WebAPI Testing

In the past...

Before WebAPI we were implementing API's using the Monorail MVC Framework (It was adopted in the application long before ASP.Net MVC had established a comparable set of features).

Monorail is very test friendly, but generally speaking our test approach was one of:

  • Constructing a controller by hand, or via some auto-registering IoC container
  • Stub/mock out the necessary mechanics of Monorail
  • Invoke the action methods directly, then check the returned values + state of the controller

It let's you focus on testing the controller in Isolation, but ignores all the mechanics such as routing, filters etc.

End to end testing

When moving to the WebAPI for the API implementation, we found it was in fact much easier to setup the entire pipeline (including all the DelegatingHandlers, routes etc.) and execute a request and get a response, here's the constructor for our base class for API tests:

Reset();

InitializeEnvironmentForPluginInstallation();

new RestAPIPluginInstaller().Install(helper);

new CoreRestResourcesPluginInstaller().Install(helper);

var host = IoC.Resolve();

jsonNetFormatter = host.JsonNetFormatter;

server = new HttpServer(host.Configuration);

client = new HttpClient(server);

And here's how a simple test looks:

[Fact]
public void Post_for_existing_package_throws_forbidden()
{
var model = new CreateOrUpdateScriptPackageModel
{
Id = new Guid("FBA8F2E7-43E8-417E-AF4E-ADA7A4CF7A9E"),
Name = "My Package"
};

HttpRequestMessage request = CreateRequest("api/scriptpackages", "application/json", HttpMethod.Post, model);

using (HttpResponseMessage response = client.SendAsync(request).Result)
{
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
Assert.Equal("POST can not be used for updates.", GetJsonMessage(response));
}
}

The GetJsonMessage method in this case just extracts the error information from the JSON response.

For tests returning full responses we used ApprovalTests with the DiffReporter - this proved incredibly productive.

[Fact]
[UseReporter(typeof (DiffReporter))]
public void Post_script_package_for_project()
{
var project = new Project {Id = new Guid("59C1A577-2248-4F73-B55E-A778251E702B")};

UnitOfWork.CurrentSession.Stub(stub => stub.Get(project.Id)).Return(project);

authorizationService.Stub(stub => stub.HasOperations((TestScriptPackage) null, CoreOperations.Instance.TestManagement.ManageScripts)).IgnoreArguments().Return(true);

var model = new CreateOrUpdateScriptPackageModel
{
Name = "My Package",
ProjectId = project.Id
};

HttpRequestMessage request = CreateRequest("api/scriptpackages", "application/json", HttpMethod.Post, model);

using (HttpResponseMessage response = client.SendAsync(request).Result)
{
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType);
Approvals.Verify(response.Content.ReadAsStringAsync().Result);
}
}

If you have not used ApprovalTests before, the magic occurs here:

Approvals.Verify(response.Content.ReadAsStringAsync().Result);

This get's the content of the response (JSON) as a string and then checks to see if it matches our "golden master" - if it does not, you are shown a Merge UI with the results of the current test compared to the golden master:

At this point you can:

  • Accept all the changes.
  • Fix what's broken and run the test again.

For this to work well you need to render your JSON with identation enabled - and you need to ensure that however you serialization works, the order of the properties in the output is repeatable.

The JsonMediaTypeFormatter that ships with WebAPI has a property called Indent you can force to true for your testing in this case (we also have it wired up for debug builds).

I think what's great about this approach is:

  • It's really easy
  • You catch errors you might miss if just checking parts of your response for consistency
  • You are reading your JSON output constantly from your application - I find this process extremely helpful - a lot of issues that were not picked up during the initial implementation/design were uncovered just by reviewing how we presented our resources in JSON
  • Did I mention it's really easy?!

Authentication

The creation of a test request was handled by a few helper methods on the API tests base class.

protected HttpRequestMessage CreateRequest(string url, string mthv, HttpMethod method, User user = null)
{
var request = new HttpRequestMessage();
request.RequestUri = new Uri(_url + url);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mthv));
request.Method = method;
request.Properties["user"] = (user ?? currentUser);

return request;
}

protected HttpRequestMessage CreateRequest(string url, string mthv, HttpMethod method, T content, MediaTypeFormatter formatter = null, User user = null) where T : class
{
var request = new HttpRequestMessage();
request.RequestUri = new Uri(_url + url);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mthv));
request.Method = method;
request.Content = new ObjectContent(content, (formatter ?? jsonNetFormatter));
request.Properties["user"] = (user ?? currentUser);
return request;
}

Notice we inject the user in the request's Properties collection, this allowed us to bypass to need to setup basic auth etc. headers and handling the additional overhead of mocking out the authentication of a user.

Is that it?

Pretty much - we certainly had more traditional Unit tests for supporting parts of the API such as the help generation, view model mapping and filters/delegating handlers - but they were very standard, the actual API testing was all done through the methods describe above...

And I think that's great news! I've worked with other technologies in the past where you could dedicate a series of posts mocking out different aspects of the underlying framework mechanics - but in the case of the WebAPI there was no need, because it can be easily self-hosted without a whole lot of bother.

Next

Next in part 8 we take a look at the "anatomy of a plugin" - investigating how we implemented support for 3rd party developers to develop API controllers as part of a plugin for Enterprise Tester.

Written on August 20, 2012