# Which tests to write first, and which can wait

> In a young product, tests belong where a bug costs money, access or data. Everything else can wait without guilt.

Published: 2026-01-26 · Source: https://subxcat.com/articles/tests-that-matter · Author: SUBXCAT

An app built with an assistant usually has either no tests at all or a lot of tests checking that a button renders. Neither helps you sleep. Tests are insurance, and insurance is worth buying for things that are expensive to lose.

A simple filter: where would a bug cost you the most, and where would you be the last to find out? That's where the first tests go.

## Money

Anything that calculates, charges or credits. Bugs here don't look like bugs. They look like a slightly different number, and someone notices a month later.

What to cover:

- price calculation: discounts, promo codes, taxes, rounding, currencies;
- payment webhook handling: a repeated event doesn't credit twice, a refund revokes access;
- balances, credits and plan limits: you can't go negative, and you can't spend the same thing twice with concurrent requests.

That last one matters most. You can't reproduce a race between two simultaneous requests by hand, but a test that fires two parallel calls can.

## Access

Tests that prove user A can't see or change user B's data. Write them against the API, not the UI, because the UI already hides those buttons.

The minimum per resource: someone else's record can't be read, updated or deleted; a regular user can't call an admin endpoint; a role sent in the request body is ignored. These tests are boring, easy to template, and they catch the most expensive class of bugs.

## Data integrity

Some of the protection here isn't tests at all but database constraints: `not null`, `unique`, foreign keys, `check`. They fire every time, not just when someone runs the suite. Start with those.

Add tests where constraints aren't enough:

- migrations: they apply to a copy of the real schema and don't lose data;
- cascading deletes: deleting an account removes what it should and nothing belonging to anyone else;
- background jobs: running one twice doesn't create duplicates.

## One or two end-to-end flows

An end-to-end test that goes through signup, the core action and payment in a real browser is slow and temperamental. But one or two of them on the main user path are worth the trouble: they catch breakage no single module can see. More than that can wait.

## What you can skip for now

- **UI snapshots.** In a young product whose interface changes weekly, snapshot tests become a ritual of updating snapshots without reading them.
- **Trivial code.** Getters, thin wrappers, date formatting you can check by eye.
- **Code you're about to throw away.** If a module is being rewritten next month, tests of its internals die with it. Keep tests of its outside behavior.
- **Coverage percentage as a goal.** The number grows fastest through code that didn't need testing.
- **Tests where everything is mocked.** If the database, the payment provider and the clock are all fakes, you're testing the fakes. For money and access, a real database in a container is better.

## Keeping tests from rotting

Tests run in CI on every push, and a red test blocks the deploy. Otherwise, a month later half of them are red and everyone has stopped noticing.

A flaky test gets fixed right away or deleted. A test that fails "sometimes" teaches the team to ignore failures, which is worse than having no test.

And one rule that pays off faster than the rest: every bug found in money, access or data first gets a test that reproduces it, and only then a fix.

If you want to know where your product needs tests first, SUBXCAT can map it out and write the first set.
