Adding a Test Suite to a Legacy App During a Rewrite
How a seven-year-old app with limited test coverage got its first real test suite during an Angular to React rewrite, and the gates that keep it green.
Summarize with:

Introduction
The most useful thing our seven week rewrite produced was not the new frontend. It was the first real test suite the product ever had.
I am writing this because the standard advice about legacy code and tests never worked for us, and I suspect it never works for most teams. The advice says stop and retrofit tests into the old code. We shipped for about seven years without doing that, and then added a real suite in weeks, because we attached it to the one event where the economics finally flip, a full rewrite.
What you'll learn:
- Why we never retrofitted tests into the legacy app, and why I think that was rational
- How a rewrite becomes the one cheap moment to buy a test suite
- The stack of gates that keeps a new suite honest, from strict types to accessibility checks
- How the suite paid for itself on the single riskiest day of the migration
- What the tests still did not catch
Seven years without a safety net
The first commit on our web app landed around March 2019. Over about seven years it grew into a large, mature Angular app: many distinct feature areas, real users in twenty four languages, live in production the whole time.
Through all of that, the app had limited automated test coverage. I want to be honest about what that meant day to day, because it is not the disaster story people expect. We shipped constantly. Quality came from manual checking and careful review. When production told us something was wrong, we reacted fast. The product grew and customers stayed.
The cost was quieter than an outage. It showed up as fear. Changes to core flows took longer than they should have, because the only way to know a change was safe was for a person to go look. The price of limited coverage is not that everything breaks. It is that everything slows down, and the slowdown hides inside every estimate.
Why we never retrofitted tests, and why that was rational
Every year the same idea came up, that we should pause and add tests to the old app. Every year it lost to the roadmap. Looking back, I think that was the right call, and it helps to say plainly why.
Retrofitting tests into a legacy app has a circular problem. To test the old code you first have to refactor it into something testable. But refactoring code with limited coverage is exactly the risk that tests exist to remove. You need the tests to do the refactor, and you need the refactor to write the tests. Teams break this loop with slow, careful tests that pin down what the old code does today, and that work is real, but it is expensive, and it competes with features every single sprint.
There was a second problem. The old app was Angular, and by the time the retrofit debates got serious, we already knew the future was React. Every hour spent making the old code testable was an hour invested in code with a shrinking future. That made the retrofit even harder to justify.
So here is the honest pattern. If a retrofit has not happened in seven years, it is not going to happen in year eight. The calm sprint where the team finally does it does not exist. Something else has to change the economics.
The rewrite was the opening
For us, the thing that changed the economics was the rewrite of the whole app from Angular to React. In early April 2026 we froze the old code, and about seven weeks later the new React and Vite stack was live, after an honest mid-course pivot away from Next.js.
Going in, we made one decision about quality. New code enters only with tests. That meant no tests later and no coverage push after launch. Tests were a condition of entry from the first feature.
A rewrite is the one moment where that rule is nearly free, for two reasons.
First, you are writing every line anyway. The usual objection to testing, that you have to disturb working code to make it testable, disappears. Nothing is settled yet, so shaping the code for tests costs almost nothing.
Second, you already have the perfect specification, the old app running right next to you. Every question about intended behavior has an answer you can go click on. Writing a test against a running reference is far easier than reconstructing intent from old code alone.
We had already proven this on a smaller surface. When we rewrote our embeddable recorder, we carved out a headless engine and built the tests before the UI existed, growing the engine suite wave by wave, from 76 tests to 119 to 212 to about 340, all green before the first React component wrapped it. The main app rewrite followed the same principle at a larger scale: the suite grows with the code, never behind it.
A suite alone is not enough. Build the gate stack.
Here is the part I would stress to anyone doing this. A pile of tests, on its own, rots. Tests get skipped and failures get tolerated, and a year later the suite no longer protects anything. What keeps a suite real is a stack of gates that every change must pass, where the tests are one layer among several.

Ours looks like this, bottom to top:
- Strict types. The new app runs TypeScript in strict mode, and a change is not done until the compiler reports zero errors. This is the cheapest gate and it catches the most.
- Architecture boundaries in lint. The app is organized into feature slices, and the lint rules fail the build on a boundary violation or an import cycle. The structure is enforced, not aspirational.
- Unit tests. Fast tests on logic and components, run on every change.
- End to end tests. Browser tests that walk real flows the way a user would.
- Accessibility checks. Automated accessibility assertions run with the end to end layer, so regressions in basic accessibility fail like any other bug.
There is a reason the stack mattered so much to us specifically. During this rewrite, a large share of the code was written by a team of named AI agents. Our QA agent, SENTINEL, reviews changes, and our pipeline runs the gate stack before any pull request is allowed to exist. One of our standing rules says a task is invalid until the type check is clean.
In the peak months of the rewrite, the team sustained around 1,700 commits a month across twelve repositories. No human can check every line at that volume. The gate stack is how you trust output you did not personally write, whether the author is a new teammate or an AI agent. That is the non-obvious lesson of this whole story. The test suite is not just a quality tool. It is the management layer that makes it safe to run development at high volume with agents writing much of the code.
The day the suite earned its keep
If you want a single moment that justified the rule, it was the cutover day in late May 2026.
That day compressed the scariest parts of the migration into a few hours: the new build tooling landed, the routing layer was swept over to its new version, the production deploy path went live, strict mode build errors were cleared, and a boot crash caused by a circular dependency was found and fixed.
The same day, the suite flagged fifty unit tests broken by migration fallout. By the end of the day all fifty were green again.
Think about what that day looks like with limited coverage. Everything moved at once, and the only verification would have been people clicking through the app, hoping to notice what changed. Instead, the damage arrived as a list of fifty precise, named failures, each pointing at the thing that broke. A test suite converts the worst day of a migration from a feeling into a checklist.
What the tests did not catch
Here is an honest accounting, because a green suite is not the end of the story.
Coverage came out uneven. The surfaces we built engine first are tested deeply. The features migrated at the highest speed are thinner, and we know it.
The old app kept serving production traffic for weeks after the new one went live, while the new stack hardened. That whole dual stack window ran on the old code, with the same limited coverage it always had. The suite protected the future, not the transition.
And the suite did not equal parity. After the cutover we still ran a manual parity pass against the old app, and it found real bugs the tests had missed, including a mobile capture issue on the recorder surface. Seven years of accumulated behavior does not fit inside any suite you can write in seven weeks. Tests told us the new code did what we said. Only comparison with the old app told us we had said the right things.
What I would tell you
- Stop budgeting for a retrofit that history says will never happen. Attach the test suite to the next rewrite or major migration instead. That is when tests are cheapest.
- Make tests a condition of entry for new code from day one. A suite you plan to add later is a suite you will not have.
- Use the old app as the specification. A running reference beats reconstructing intent from old code every time.
- Build the gate stack, not just the tests: strict types, enforced boundaries, unit tests, end to end tests, accessibility checks. The stack keeps the suite honest.
- If AI agents write part of your code, the gates are your management tool. Review the gates and the diffs that matter, not every line.
- Let the old code die untested. Spending quality effort on code you are replacing is investment in a shrinking asset.
- Do not confuse green with done. Run a real parity pass against the old behavior anyway.
Conclusion
For seven years the honest description of our web app was a large, mature product with limited test coverage. Nothing about ordinary roadmap life was ever going to change that. The rewrite did, because for seven weeks the economics of testing flipped, and we took the deal. Every new line arrived with its tests, behind a stack of gates that still runs on every change today.
If you are staring at a legacy app and feeling guilty about coverage, my suggestion is to stop planning the retrofit. Plan the moment instead. When the big migration comes, and it eventually comes for every codebase, make the test suite a non-negotiable part of the deal.
Frequently Asked Questions
Further Reading
Tags
Related Articles
Try Our Free Tools
AI Video Prompt Generator
Generate production-ready AI video prompts through conversation. Optimized for Sora 2 and Gemini video generation
AI Video Analyzer
Analyze video content frame-by-frame with AI. Content moderation, security monitoring, accessibility, and product demos
Text Language Detector & Translator
Detect any language and translate text instantly with browser-based AI