Many teams ship without end-to-end tests. The unit tests are green, the pipeline passes, the release goes out. Then production tells a different story.
I’ve seen it often enough that it no longer surprises me. Skipping E2E tests has become normal. I think it’s an expensive habit.
Contract tests are your safety net. E2E tests are the proof that the net actually holds.
The illusion of green#
A unit test tells you that a piece of code does what the developer thought it should do. It doesn’t tell you that the system does what the consumer expects.
Take an API that creates an application role with a validity limit. Every layer can pass its own tests:
- The request validator accepts the field.
- The mapper passes it along.
- The downstream system receives a payload.
All green, and the feature can still be wrong. Maybe the field is accepted but never enforced. Maybe a default overrides it. Maybe the downstream system treats it as a cap instead of a timer. No unit test will catch that, because no unit test sees the whole path.
Your contract is more than a schema#
“We have an OpenAPI spec, so the contract is covered.” I hear it a lot. But a spec describes the shape of the contract. It says nothing about the behavior.
A real contract also covers:
- What happens when a field is missing, invalid or combined with another field
- Which defaults apply
- Which status codes and error messages come back
- What actually ends up in the downstream system
E2E tests make that behavior explicit and executable. Each test case is a statement: given this request, the system responds like this, and the end state is that. When someone changes the behavior, a test fails and the change gets noticed. Without that, your contract is a hope.
Integrations break at the seams#
Most production incidents don’t start with a bug inside a function. They start in the space between components:
- A renamed field in a downstream API
- A changed default in a configuration template
- A date format that one side reads differently from the other
- An assumption about validity, ownership or ordering that nobody wrote down
Only a test that runs through the real stack, from request to end state, covers these seams. Mocks can’t. A mock only knows what you already believed.
“But E2E tests are slow and flaky”#
They can be. That’s usually a design problem, not a law of nature. A few things help:
- Keep them focused. Test behavior and contract, not every permutation. Leave the edge-case logic to unit tests.
- Make them declarative. Test cases written as data (request in, expected response and end state out) are easy to read, review and extend.
- Control your data. Isolated, repeatable setup and cleanup remove most of the flakiness.
- Run them where they matter. On every merge or before a release, not necessarily on every keystroke.
A small, stable E2E suite beats a large one that everybody ignores.
The real benefit: confidence and speed#
Teams without E2E tests often feel fast, right up until they have to change something. Then every change is scary, manual regression checks creep in and releases slow down.
Teams with good E2E coverage can refactor, upgrade dependencies and change templates, because they have a fast answer to the only question that matters: did we break anything?
E2E tests also double as living documentation. A new colleague can read the test cases and learn how the API is meant to behave, and the tests are usually more accurate than any wiki page.
Skipping them doesn’t make the cost go away#
Not writing E2E tests doesn’t remove the cost. It moves it to:
- Production incidents
- Manual testing before every release
- Long debugging sessions across system boundaries
- Consumers who stop trusting your API
That cost arrives later, it’s higher, and it lands on the people least able to absorb it.
Where to start#
You don’t need full coverage on day one.
- Pick your most critical flows, the ones that hurt most when they break.
- Write one E2E case per flow, covering the happy path and the most important failure.
- Add a case for every bug that slips through, so it can’t come back.
- Run the suite in your pipeline and treat a failure as a stop signal, not a suggestion.
Prove the whole, not just the parts#
Unit tests protect your code. Contract tests protect your interface. E2E tests protect your promise to the people who depend on you. If you only have the first two, you’re guessing about the third.
Not having E2E tests is common. Common isn’t the same as good. The teams I trust most are the ones that can prove their system works as a whole, not just in parts.
Does your team run E2E tests? What made it work, or what made you give up? Let me know in the comments.
