All Articles

Integration Testing an API

In engineering organizations large enough to have multiple independent teams, there is a costly class of bugs that occurs at the interface between two teams. These bugs aren’t logical errors, but instances of miscommunications between the two teams.

Let’s say one team is producing an API for the other team to consume. Typically these bugs represent a subtle misunderstanding between how the API should behave.

For example, we might have a REST API for a simple to-do app that contains an endpoint for creating a task:

POST /tasks

{
    "id": <uuid>,
    "name": "Buy groceries",
    "description": "I need to go to the store and get groceries"
}

What about the case where the client tries to create a task with a duplicate name? The backend team might think this case should not be allowed and return a validation error while the frontend team might believe this is a valid case and expect the request to succeed.

In my experience, a strategy for minimizing these bugs is encouraging communication between the two teams by:

  • forcing the producing team to explicitly define the API they are going to build
  • forcing the consuming team to explicitly define the operations they need to be implemented

Development Workflow

When starting a new project with multiple teams, we can use a workflow for facilitating this communication and break the project into three phases: Definition, Development, and Integration.

1. Definition

The definition phase is where two teams agree on what operations the consuming team needs and that the proposed API design will satisfy those use cases.

  • The producing team puts up a pull request with an API definition (including validation errors) and the consuming team reviews.
  • The consuming team puts up a pull request with a facade interface definition and the producing team reviews. This facade should describe the various specific operations the team needs.

2. Development

After the teams have agreed on what needs to be built we can start executing.

  • The consuming team mocks the facade, writes opaque tests against the facade interface, and develops against it.

    • These tests should use valid test data and not rely on implementation details so that they will continue to pass against the real implementation.
  • The producing team implements the general purpose API and writes opaque tests against the API.

3. Integration

After development of the API has been completed, the consuming team can integrate their codebase with the real API.

  • The mocked facade implementation is removed and an actual implementation is wired up to the API.
  • Note: the facade tests should continue to pass at this point.

Conclusion

This might be a strategy for you to explore if you have noticed instances of these communication errors with your team. It can be a costly exercise and might not be worth the cost/benefit, but it does provide real value.

Pros

  • Each team codifies their understanding of how the API should behave which prevents miscommunication
  • Forces the consuming team to not be passive
  • Forces the producing team to understand the calling behavior of their consumers
  • The consuming team develops against the facade, allowing them to be unblocked and work concurrently with the producing team
  • Allows the producing team to build a general purpose API and the consuming team to consume a niche facade