Frontend/backend contract testing
Tags: tdd, contracttesting
Published: 2023-11-10 09:00:00

---
title: Frontend/backend contract testing
published: true
description: How to cover both frontend & backend API layer with the same suite of automated tests
tags: TDD, contracttesting
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1ya1ftpxa4w4uuzrd5sq.jpeg
# Use a ratio of 100:42 for best results.
published_at: 2023-11-10 09:00 +0000
---

Cover photo by <a href="https://unsplash.com/@othentikisra?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">israel palacio</a> on <a href="https://unsplash.com/photos/two-square-blue-led-lights-ImcUkZ72oUs?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Unsplash</a>
  

## Context

Contract testing is a known concept. It is nicely described in the [Contract Tests](https://martinfowler.com/articles/practical-test-pyramid.html#ContractTests) section of the excellent article "The Practical Test Pyramid" by [Ham Vocke](https://www.hamvocke.com/).

Takeaway points:

1. Test consumer & producer *together*
2. Test *only* the contract layers, without the behaviour of either the consumer or producer

At [Software Sauna](https://softwaresauna.com/) we developed a similar approach, albeit with some modifications to fit our own flow of developing web apps:

1. We do not test on the HTTP layer - tests interact with a domain-specific layer which hides the data transport infrastructure (here called *API functions*).
1. We do not setup test-double behaviour on the producer side. Rather, we expose test utility endpoints and drive test double behaviour from tests directly so both input & expected data are visible in test.

## The web app

Software Sauna predominantly produces web apps & not distributed systems, so most of contracts we deal with are those between the web UI usually called *frontend* & the behaviour & busines logic of the app usually called *backend*.

The frontend communicates with the backend by sending HTTP requests.

![Frontend -> HTTP -> Backend](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r4bgbugveifbgn4f9g4i.png)

We want readable & maintainable code, so we slice our frontend & backend codebases into abstraction layers. This results in *[seams](https://wiki.c2.com/?SoftwareSeam)* throughout the codebases, enabling us to have tests on relevant scopes.

One such seam is between code that implements behaviour (does stuff) & code that deals with HTTP communication.

![Seams in frontend & backend codebases](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p1ocdetrr8309bfnirl0.png)

Now we can look at the frontend's API-related layer, the HTTP channel & backend's API-related layer as a single component - the *contract*.

![Frontend logic -> Contract -> Backend logic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5aw05xqd77vfj4e5pjdy.png)

The contract has one responsibility, that is to transfer data from frontend to backend & back.

However, other things related to this communication are also part of it, like authentication, authorisation, throttling, etc.

Here is an example of a web app stack with the contract components in more detail:

![Web app stack with marked scope of API tests](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3rbtk3wb5l1mvsrsw38t.png)

To test the contract, we just replace the frontend & backend behaviours with tests & [test doubles](https://martinfowler.com/bliki/TestDouble.html), respectively:

![Frontend test -> Contract -> Backend test double](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6i6kuk8hdqwkug16ne6u.png)

## Example test

Every API test has two parts:

1. Frontend test
1. Backend test double replacing the actual service

The test in the example tests the API function `getUsers()`, which will be called by components who want to display a list of the system's users.

### Test

{% embed https://gist.github.com/mbjelac/95cc7a45529c187b7e9de2346f63099f %}

*Line 9:* Log in as a specific user. This tests both frontend & backend authentication implementation & config. It is practical to create a single dummy user for each user role in the app.

*Line 16:* Stub the method labelled `get-users` with some test data.

*Line 18:* Call the API function which should result in a method call to the appropriate backend service (actually test double).

*Line 20:* Assert that the stubbed data is returned from the API function.

There is also an example of an unauthorised call to show that we can test the backend's authorisation config.

### Test double

{% embed https://gist.github.com/mbjelac/b30f68c2cb17376a095c33e061a71189 %}

This example uses Spring to auto-wire services (actually, their test doubles) into the DI context.

The method labels (as string parameters in the `withMethod` calls) have to match string parameters in frontend-side calls to `setStubbing` & `failWith`. If they don't, the tests will fail.

## Spying

Sometimes we want to know if the desired arguments were passed to the backend method.

{% embed https://gist.github.com/mbjelac/b111b38303ec17d40102dd1bb12bf984 %}

Our backend test double acts both as a stub and a spy, enabling the frontend test to check returned values & also check that the correct arguments were sent.

## Other stuff

### Testing error handling

Similar to stubbing values, we can tell the backend test double that a method should fail. This allows us to test:

- backend mapping of failures (i.e. exceptions) to HTTP error responses
- frontend handling of errors

Part of error handling was already shown in the example for unauthorised calls.

### Test-driving the API layer

This approach allows the API layers to be implemented within a TDD workflow.

We test-drive a UI component. The component requires data from the backend. As this is crossing the seam, we test-drive the implementation using a test double to replace the API function.

That test double defines the API function signature.

Then we can test-drive the API function itself, by providing a test-double on the backend side, which in turn defines the service method signature.

## Conclusion

During our usage of this pattern in Software Sauna, we have found that it completely eliminates common errors like request/response structure definition discrepancies, but also harder to find errors buried in URL path & header definitions, error handling, authentication & authorisation setup.