Unit test your service integration layer

Published: 2015-01-05 by Lars  thoughtstesting

One of the complaints I hear most often about unit testing, is that its focus is too narrow. For instance, when testing a JavaScript front-end, the unit tests will make assumptions about the contract with the back-end API, typically by mocking the HTTP requests in the unit tests for the API integration layer. But those assumptions can turn wrong over time, without any unit tests failing. Then the complainer typically concludes that we therefore need quite of lot of integration tests too which would have failed in such a situation.

However, I like to follow the testing triangle of Mike Cohn, and keeping the number of integration tests to an absolute minimum. Integration tests tends to be slow and brittle, compared to the speed and robustness of unit tests. This post describes one way we can write unit tests for our service integration layer that are guaranteed to fail as soon as the contract is broken.

I will continue to use the example of a JavaScript front-end talking HTTP to a web server, but the technique I describe applies equally well to other scenarios where our code has an out-of-process dialog to an external service using a defined protocol.

Consider a scenario where our server implements a web service that returns a JSON representation of a name. It might have been implemented originally to return an object that looks like this: {name: "Lars Thorup"}. We will have tests on the back-end that ensures that this is what the API actually returns. When writing a unit test for the front-end code that invokes this API, we have to mock the service call, typically hard coding a return value like this: {name: "Lars Thorup"}. The test will then verify that our service integration layer does the necessary data transformation with this data. This scenario can be illustrated like this:

One day the web service gets updated to implement a more refined version of the API. Let's say that it now returns an object that looks like this: {first: "Lars", last: "Thorup"}. The back-end tests will be changed as well to avoid the build from breaking. However, if we forget to change the front-end code and the front-end tests to reflect this API change, the build will not be affected at all. This new scenario looks like this:

To solve this problem, we should stop hard-coding the mocks in our service integration layer tests. After all, we don't actually know that this is what the service will return, we just pretend. It is better to have the mock return something that the back-end will actually return, and we can arrange for that by generating valid request-response pairs.

The solution can be illustrated like this:

When running the back-end unit tests, we make sure that for each scenario that we test, we also record the actual request and the actual response. These can be recorded in a text file or somewhere else. After each build, this text file now contains a list of valid request-response examples. The build should make this file available to downstream builds as a build artifact in the build repository. The client test should then use the request-response pairs in this file to act as mocks for the service. This can work, if the tests are written so that it leverages exactly those same requests that are used in the back-end unit tests. In the case that the front-end tests need an extra request-response example, it should be added to the back-end unit tests so it will be automatically available and valid after the next back-end build.

To illustrate how this can be done in practice with the back-end server written in Node.js and the front-end in JavaScript, take a look at a sample project, mars, that I've made available on GitHub. Here the back-end tests (scenario.test.js) does all its tests through a common module (api.proxy.js), which records all the request-response pairs in the traffic variable which finally gets written to a file (api.sample.json, generated, so not in Git). The client tests (demo.model.test.js) uses a common module (api-faker.js) to load api.sample.json and automatically generate a mocked API from all the request-response pairs.

Now the client unit tests will break as soon as a breaking change is introduced into the API contract.

Edit: I recently gave a presentation about this, find the slides here.

Discuss on Twitter