Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

You know when they tell you exposing an HTTP API for your application (usually a REST-like one) is positive for reuse and accessing it in unforeseen ways? That's whay just happened to me last week while trying to put together some functional tests.

The context: architecture

This application is divided between multiple components on the server-side, that communicate with each other, sometimes asynchronously. However, only one of them faces outside.

A rich-client UI, written in JavaScript, runs in a browser and accesses this port of the application. It communicates with the server-side through a REST-like API, that as such does not have any session persistence. The data transmitted is contained in POST parameters, for example, but mostly in the body of PUT and GET requests in the form of JSON.

Behavior can be driven a bit by HTTP itself, as it provides semantics that a browser should respect (redirects with the Location header, POST and GET semantics respected, etc.). This architecture is really nothing new and revolutionary, but it's surprising to find it implemented consistently.

Goal

My objective was writing functional tests for checking the happy path integration of the server-side components. The UI has its own tests, and we already use a bit of Selenium magic for running it in a browser and test the basic integration with the server.

How

Well, once a REST-like API is exposed, you can very easily adopt an HTTP library for your language of choice and start writing tests like you were a browser bot:

make HTTP calls: POST, GET, PUT, or DELETE according to the action required.

Encode and decode JSON (or XML) into your own testing framework, which may not even be written in the same language as the application (this would be an extreme case.)

Write assertions on the data returned, but also on the state of the conversation: status codes, HTTP response headers and everything that can be specified about the API.

So far, it's all roses, Agile and let's hug trees. But there are also problems to solve in order to write fast, reliable end-to-end tests.

Resolved issues

The first issue to solve for writing these tests is that of asynchronicity. Since the components of the server side talk with each other multiple times, it's normal for the UI to (long) poll or listen to the server for updates.

So just executing a request isn't going to cut it: we may check the result too early. Synchronization of end-to-end tests basically means waiting for a condition to be satisfied, to eliminate the non-deterministic behavior of tests that may pass once and then fail randomly in the middle of a test suite:

while (!condition()) {
sleep(1);
seconds++;
if (seconds > timeout) {
fail("We tried waiting for $condition to be fulfilled, but after $timeout seconds there was still no result, so we declare the test failed.");
}
}

The next issue is that of an API that does not expose the right commands because some logic is embedded in the UI. For example, I found a wrong redirect performed with the Location header: this was actually working in the browser, but just because of particular logic embedded in the JavaScript client. We can say that having tests as the second client for your API allows you to improve it: it's true that one of the most pwowerful ways to test the robustness of a design is to change the pieces connected to it.

The third problem was server state: it is usually not a problem with REST or web services, as they are naturally concurrent and can be accessed by many clients at the same time. However, some conflicts between resources may arise if you reuse the same data to repeat the test.

Think for example of creating a user with a given social security number: your system shouldn't let you create a resource representing him multiple times. Rather than than changing the test data each time, and lose build repeatability, it's easier to reset the database to a known state at the start of each test.

It's also crucial to be very precise in resetting the database, for speed and isolation of tests. You should only clean up what is strictly related to the current test: think of a future in which your test could run in parallel.

Finally, I advise you to clean up the server state at the start of the test instead of in the tear down phase as unit test do. This allows you to inspect the final state of the server after a test is failed, while being functionally equivalent for the test execution.

Conclusions

When you hear yet another talk or article about writing an HTTP API, don't jump to conclusions: it's not necessarily an hyped architecture, but can be a way to separate server and client concerns, to test the latter without encountering the former in the way.

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.