hor-backend-testing · git:20261016.ade0c6a · 2026-10-16 · sha256 d26c592cf05c2164

hor-backend-testing git:20261016.ade0c6aA

Immutable. This exact content never changes and is served at /api/v1/blob/d26c592cf05c2164.

---
name: hor-backend-testing
description: >
  Organize and run the backend repository's tests: where a test file goes (tests/__tests__ for
  tests that do not write to the database, tests/_orders for tests that do), how run order among
  DB-writing tests is guaranteed, how to run the whole suite or a single test, and the purity
  rules for tests and their doubles. Use whenever the user asks where to place a test, how to
  guarantee test order, how to run tests, or how to keep tests pure. Manual E2E verification is
  out of scope.
---

# Backend Testing

A skill for **implementing tests in a backend repository**: **where** each test file belongs, **how**
DB-writing tests are ordered so they do not corrupt each other, **how** to run them, and the
**purity** rules that keep a failing test meaningful. It governs test *organization and discipline* —
the *writing style* of an individual test (describe/test structure, case data, naming) follows the
project's own Jest conventions and is out of scope here.

> This skill states a **general, project-independent rule**, not one repo's current tree. The
> directory names (`tests/__tests__`, `tests/_orders`, `tests/mocks`, `tests/tools`) are the
> **recommended layout** — map them onto your project. Sample code follows the project's lint style
> (no semicolons, 2-space indent, trailing commas).

## Core principle: a test fails only when the code under test is wrong

A test must fail **only** because the code it exercises is wrong — not because another test ran
first, and not because the test itself contains untested logic. Two rules enforce that, and
everything below follows from them:

1. **Isolate database state.** A test that **writes to the DB** mutates shared seed data that other
   tests read, so it is placed **apart** from tests that don't, and **ordered** among its peers.
2. **Keep tests pure.** A test may only exercise code that is **itself already tested** — no logic is
   invented inside a test, and every mock/tool it leans on has its own test cases.

## 1. Placement & execution order

Split every test by one question: **does it write to the database?**

| Location | Contents | Ordering |
| --- | --- | --- |
| `tests/__tests__/` | tests that **do not** write to the DB (pure units, read-only) | order-independent — run in any order |
| `tests/_orders/<Category>/` | tests that **do** write to the DB | order-**guaranteed within a category** via a `_.test.js` barrel |

- **Why the split.** A DB-writing test changes the shared `development` / `dev-master` seed fixtures,
  so a later test can observe a mutated database. Read-only tests can't interfere, so they need no
  ordering; DB-writing tests must be ordered.
- **The order barrel.** Each category folder holds `_.test.js` that **imports its test files in the
  order they must run** — that import order *is* the execution order within the category.
- **Cross-category order is NOT guaranteed.** `_orders` fixes order *within* a category only. If two
  categories can interfere, isolate them into **separate CI jobs** — add/update a
  `.github/workflows/*.yml` per category so each runs against its own fresh database.
- **Verification against real middleware belongs in neither location.** Exercising the product
  against a real broker, search cluster and change-data-capture pipeline needs those services brought
  up in Docker and is done **by hand, outside `tests/` entirely** — see the E2E environment
  convention. No such file is added here, and `npm run test` stays runnable with nothing but Node
  installed.

Details, the barrel example, and the per-category workflow snippet are in
[placement-and-ordering.md](./references/placement-and-ordering.md).

## 2. Running tests

- **Whole suite**: `npm run test` — rebuilds the DB (teardown → migrate → seed) and runs everything on
  **SQLite** (the local default). Authoritative, slowest.
- **A single test (fast, no rebuild)**: iterate by invoking Jest directly against one file —
  `NODE_OPTIONS="--experimental-vm-modules" NODE_ENV=development npx jest <path>` (add `--runInBand`
  for DB-writing/order-sensitive tests). It runs against the DB **as it currently is**, so prepare the
  DB once and reset it piecemeal (migrations-only / one-seeder-set) instead of a full rebuild.
- **Live (real-dialect) run**: running against the database the project's specification names
  (MariaDB, MySQL or PostgreSQL) instead of SQLite **requires that database running locally**,
  selected with `NODE_ENV=live`. Test against the one the specification names, never another
  database of the same family. How that database is stood up is not part of this convention.
- **Parallelism × per-worker heap must fit in real memory.** Jest runs one worker per core by
  default, and `--max-old-space-size` is **not a reservation — it is how far the GC may be
  deferred**, so a value the machine cannot actually give lets every worker grow until it crashes
  the machine (one worker alone can do it). Derive the cap from a **measured peak**, check that
  workers × cap fits in the memory actually free, and re-check as the project grows: per-worker
  usage rises with every model and seeder added, so a pair that fit at the start can later crash the
  machine.

The exact commands, the suite runner's steps, the fast single-test loop with the piecemeal DB
reset, and how to size the worker/heap budget are in
[running-tests.md](./references/running-tests.md).

## 3. Test purity & test doubles

- **No new logic in a test.** Never define a function or class inside a test to compute an expected
  value or stand in for production behavior — an untested helper can be wrong, making the test lie.
  Assert against literal expectations.
- **Everything a test uses must already be tested.** Every function, class, mock, and tool the test
  leans on has its own test cases.
- **Doubles live under `tests/`, and are themselves tested.** Mocks go in `tests/mocks/`, shared test
  tools/factories in `tests/tools/`, and **each mock class has its own test file**. Prefer explicit
  fakes (obviously-fake names/values).

The rule with good/avoid examples, and the mocks/tools layout, are in
[purity-and-mocks.md](./references/purity-and-mocks.md).

## Finishing checklist

- [ ] The test is in `tests/__tests__/` if it does no DB write, or `tests/_orders/<Category>/` if it does ([§1](#1-placement--execution-order)).
- [ ] A DB-writing test is imported (in the right position) by its category's `_.test.js` barrel.
- [ ] If the new test's category can interfere with another, a per-category CI workflow isolates them.
- [ ] The test defines **no** new logic; every function/class/mock it uses is already tested ([§3](#3-test-purity--test-doubles)).
- [ ] Any new mock/tool lives under `tests/mocks/` or `tests/tools/` and has its **own** test file.
- [ ] Verified with a single-test run ([§2](#2-running-tests)).

## Detail files

- [placement-and-ordering.md](./references/placement-and-ordering.md) — `__tests__` vs `_orders`, why
  DB-writing tests need ordering, the `_.test.js` order barrel, cross-category isolation via
  per-category CI workflows (§1)
- [running-tests.md](./references/running-tests.md) — the whole-suite runner and what it does, running
  a single test with the dev/master seed sets, resetting the database piecemeal, sizing the
  parallelism × heap budget against real memory, and what a live (real-dialect) run requires (§2)
- [purity-and-mocks.md](./references/purity-and-mocks.md) — no-new-logic and only-tested-code rules
  with good/avoid examples, and the `tests/mocks` / `tests/tools` layout where doubles are themselves
  tested (§3)