@boopkit/cap-testing 0.5.7 → 0.5.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,74 +2,32 @@
2
2
 
3
3
  **Part of [Boop](https://www.npmjs.com/package/@boopkit/cli)** — a bootstrap platform for AI-first builders. `boop init` scaffolds a running Node/TypeScript web app from composable **capabilities**, each one bundling the code, dependencies, middleware wiring, and agent skills needed for a feature. You pick the capabilities; `boop add` handles the rest.
4
4
 
5
- `cap-testing` sets up Jest for a TypeScript project and installs the testing skills agents need to use it well. It's mostly skills — the testing philosophy, the Light TDD loop, and Express-specific patterns — with a small scaffold so `npm test` works from the first commit.
5
+ `cap-testing` installs the testing method: the skills that tell an agent how to test well, in any language. It ships no test runner. The Jest toolchain comes from [`cap-typescript`](../cap-typescript/README.md), so a project in another language, such as a Swift iOS app, can have the method without Jest.
6
6
 
7
7
  ## What You Get
8
8
 
9
- Agent skills plus a minimal Jest + ts-jest scaffold. The scaffold is intentionally thin: one sample test, one config file, one test-specific tsconfig — enough that `npm test` runs green on day one and new tests follow a known layout.
10
-
11
- ### Scaffold files
12
-
13
- Copied into your project on install:
14
-
15
- - `jest.config.js` — ts-jest preset, Node test environment, test matcher at `tests/**/*.test.ts`, and the worker settings the suite runs under (`maxWorkers: 4`, `workerIdleMemoryLimit: '500MB'`). Those two spread test files across a pool of worker processes Jest recycles as they grow, so a long suite's memory stays bounded instead of climbing until it dies. They live in the config rather than in the `test` script so that focused runs — `npx jest tests/foo.test.ts` — inherit them too. Because files now run concurrently, tests must namespace whatever they create; drop `maxWorkers` to 1 if a suite is not isolation-safe yet.
16
- - `tsconfig.test.json` — extends your project tsconfig and includes `tests/`, widening `rootDir` to the project and naming its `@types` packages so that `src/` and `tests/` compile as one program on any TypeScript major. Its comments explain each setting.
17
- - `tests/app.test.ts` — a starter test asserting the Express app boots and has body parsing wired
18
-
19
- ### Scripts added to `package.json`
20
-
21
- | Script | Command |
22
- |--------|---------|
23
- | `test` | `jest` |
24
- | `test:watch` | `jest --watch` |
25
- | `typecheck` | `tsc -p tsconfig.test.json` |
26
-
27
- `typecheck` is the whole-program static check the testing and planning skills tell every task to run alongside its focused tests: it sees every caller of what changed, which is the breakage a focused run is blind to.
28
-
29
9
  ### Skills installed
30
10
 
31
11
  Copied into `skills/` on install:
32
12
 
33
- - `test.testing.skill.md` — the testing philosophy: functional tests over ceremony, observe failures before claiming success, mocks as a last resort, and a checklist for when the spec is silent (resolve the ambiguity first, then test).
34
- - `test.tdd-light.skill.md` — the Red → Green → Verify loop, including bug fixes (write the failing test before the fix).
35
- - `test.testing-express-web.skill.md` — Express-specific guidance: how to run focused Jest commands, test file layout, and patterns for services, controllers, middleware, and HTTP routes.
36
-
37
- ## Requirements
38
-
39
- **Other capabilities:** none. The sample test expects `src/app` to export an Express app, which is provided by `cap-api` in the default `web-app` profile. If you add `cap-testing` to a project without an Express app, edit or delete `tests/app.test.ts`.
13
+ - `boop-testing-testing` — the testing philosophy: functional tests over ceremony, observe failures before claiming success, mocks as a last resort, which tests to run when, and what to do when the spec is silent.
14
+ - `boop-testing-tdd-light` — the Red → Green → Verify loop, including bug fixes (write the failing test before the fix).
40
15
 
41
- **npm devDependencies added to your project:**
16
+ Express-specific test guidance ships with `cap-web` as `boop-web-testing`.
42
17
 
43
- - `jest@^30` — test runner
44
- - `ts-jest@^29` — TypeScript preset for Jest
45
- - `@types/jest@^30` — TypeScript types for Jest
46
- - `typescript@^5` — required by `ts-jest` and by the `typecheck` script
47
-
48
- Each is pinned to a caret major so that the toolchain a new project receives is the one boop runs its own suite against, rather than whatever shipped most recently.
49
-
50
- **Environment variables:** none required for the scaffold. Individual tests may need service credentials (the Express testing skill documents the integration-test prerequisites — local Postgres, etc.) — those are project-specific and not enforced by this capability.
18
+ ## Requirements
51
19
 
52
- ## Usage
20
+ **Other capabilities:** none.
53
21
 
54
- Run the suite:
22
+ **npm dependencies:** none.
55
23
 
56
- ```bash
57
- npm test # full suite
58
- npm run test:watch # watch mode
59
- ```
60
-
61
- Targeted runs (documented in `test.testing-express-web.skill.md`):
62
-
63
- ```bash
64
- npx jest tests/services/foo.test.ts --no-coverage
65
- npx jest tests/services/foo.test.ts -t "handles replay"
66
- ```
24
+ ## Projects from before the split
67
25
 
68
- New tests go under `tests/`, mirroring the `src/` layout (`tests/services/foo.test.ts` for `src/services/foo.ts`). The three skill files are the canonical guide — agents should read `test.testing.skill.md` for philosophy before writing any test, `test.tdd-light.skill.md` to stay in the Red → Green → Verify loop, and `test.testing-express-web.skill.md` for the project-specific layout and commands.
26
+ Until version 0.5.8, `cap-testing` also shipped `jest.config.js`, `tsconfig.test.json`, `tests/toolchain.test.ts`, the Jest dev dependencies and the `test`, `test:watch` and `typecheck` scripts. Those now ship in `cap-typescript`. `boop update` adds `typescript` to a project that holds any of those files from `testing`, and the files move into it unchanged, so the project keeps everything it had. A project that adds `testing` after the split does not receive `typescript`.
69
27
 
70
28
  ## Added by
71
29
 
72
- Included by default in the `web-app` profile.
30
+ Included by default in the `web-app` and `development` profiles.
73
31
 
74
32
  Add to an existing project with:
75
33
 
package/capability.json CHANGED
@@ -1,41 +1,26 @@
1
1
  {
2
2
  "name": "testing",
3
- "description": "Jest testing setup",
3
+ "description": "Testing method: the testing principles and the TDD-Lite workflow",
4
4
  "type": "development",
5
5
  "dependencies": [],
6
- "devDependencies": [
7
- "jest@^30",
8
- "ts-jest@^29",
9
- "@types/jest@^30",
10
- "typescript@^5"
11
- ],
12
- "files": [
13
- "jest.config.js",
14
- "tsconfig.test.json",
15
- "tests/"
16
- ],
6
+ "devDependencies": [],
7
+ "files": [],
17
8
  "skills": [
18
9
  "boop-testing-tdd-light",
19
- "boop-testing-testing-express-web",
20
10
  "boop-testing-testing"
21
11
  ],
22
- "scripts": {
23
- "test": {
24
- "cmd": "jest",
25
- "description": "Run all tests"
26
- },
27
- "test:watch": {
28
- "cmd": "jest --watch",
29
- "description": "Run tests in watch mode"
30
- },
31
- "typecheck": {
32
- "cmd": "tsc -p tsconfig.test.json",
33
- "description": "Type-check src/ and tests/ as one program"
34
- }
35
- },
36
12
  "skill_migrations": {
37
13
  "boop-testing-tdd-light": "test.tdd-light.skill.md",
38
- "boop-testing-testing-express-web": "test.testing-express-web.skill.md",
39
14
  "boop-testing-testing": "test.testing.skill.md"
40
- }
15
+ },
16
+ "file_moves": {
17
+ "skills/boop-testing-testing-express-web/": {
18
+ "capability": "web",
19
+ "path": "skills/boop-web-testing/"
20
+ },
21
+ "jest.config.js": { "capability": "typescript" },
22
+ "tsconfig.test.json": { "capability": "typescript" },
23
+ "tests/toolchain.test.ts": { "capability": "typescript" }
24
+ },
25
+ "split_into": ["typescript"]
41
26
  }
package/package.json CHANGED
@@ -1,15 +1,13 @@
1
1
  {
2
2
  "name": "@boopkit/cap-testing",
3
- "version": "0.5.7",
4
- "description": "Jest testing setup and testing skills for AI agents",
3
+ "version": "0.5.9",
4
+ "description": "Testing skills for AI agents: the testing principles and the TDD-Lite workflow",
5
5
  "files": [
6
- "scaffold/",
7
6
  "skills/",
8
7
  "capability.json"
9
8
  ],
10
9
  "boopkit": {
11
10
  "capability": true,
12
- "scaffoldDir": "scaffold",
13
11
  "skillsDir": "skills",
14
12
  "manifest": "capability.json"
15
13
  },
@@ -17,7 +17,7 @@ Red → Green → Refactor. Document "before" and "after" states.
17
17
  2. **Green**: Implement the minimum change. Add any additional tests.
18
18
  3. **Verify**: Re-run all tests from steps 1-2. Type-checking alone is **not sufficient** — functional tests must pass. Check for regressions in related functionality. Verify from the user's perspective — don't just test internal state. For example, if the feature writes files the user interacts with, verify those files exist and are correct.
19
19
 
20
- Test tools: `npm test` for the whole suite, `npx jest <path>` for a focused run. Go through `npm test` rather than the runner binary when you want the suite — the script and `jest.config.js` together carry the settings the suite is tuned for, including how many files run at once and how much memory a worker may hold before Jest replaces it, and a bare runner invocation drops whatever lives only in the script. The general testing skill explains why those settings matter and which of them is load-bearing. If the project defines its own test commands beyond these, its `package.json` is the authority. For UI changes, coordinate manual browser testing.
20
+ Test tools: `npm test` for the whole suite, a focused run that names the test files for anything narrower, and `npm run typecheck` for the whole-program static check. The project's stack capability supplies `npm test` and `npm run typecheck` as npm scripts, and its testing skill gives the exact form of a focused run (`boop-typescript-testing` in a TypeScript project). Go through `npm test` rather than the runner binary when you want the suite — the script and the runner's config file together carry the settings the suite is tuned for, including how many files run at once and how much memory a worker may hold, and a bare runner invocation drops whatever lives only in the script. The general testing skill explains why those settings matter. If the project defines its own test commands beyond these, its `package.json` is the authority. For UI changes, coordinate manual browser testing.
21
21
 
22
22
  ## Picking the right test shape
23
23
 
@@ -30,4 +30,4 @@ The cycle above says "write a test." That choice — *which kind of test* — is
30
30
 
31
31
  For the principles behind these choices — when each shape is required, when each is insufficient, why "tests covered the function's contract in isolation" is not enough at a producer/consumer boundary — see [general testing principles](../boop-testing-testing/SKILL.md). Read it before picking a shape on any task that touches code with callers elsewhere in the repo.
32
32
 
33
- For domain-specific test mechanics in this project's Express stack — fixtures, real-service setup, route helpers, schema contracts — see [Express web tests](../boop-testing-testing-express-web/SKILL.md).
33
+ For the mechanics of testing on this project's stack, see its testing skill: `boop-typescript-testing`, which the `typescript` capability installs, for the test runner and the type check, and `boop-web-testing`, which the `web` capability installs, for an Express app's fixtures, real-service setup and route helpers.
@@ -18,7 +18,7 @@ metadata:
18
18
 
19
19
  - **Observe the failure first.** A plausible explanation for repeated failures is still a hypothesis until you see success. Always verify the expected *positive* outcome actually occurs (e.g., `cache_hit: true`, not just "no errors").
20
20
  - **Mocks are a last resort**, acceptable only for genuinely external dependencies (third-party HTTP, email providers), to pin a nondeterministic input the test is not asserting on (a model/LLM call in a plumbing test — see "Stub the model, not the machinery"), or to isolate complex decision logic (error classification, retry semantics). If it doesn't hit a real store, call it a unit test — not an "integration test with mocks."
21
- - **Data-driven coverage for config tables.** Code backed by configuration (pattern tables, route maps, enum mappings) must have tests that exercise *every entry*, not a hand-picked subset. Include a count sanity check (`expect(records.length).toBeGreaterThan(N)`) so an empty response doesn't silently pass.
21
+ - **Data-driven coverage for config tables.** Code backed by configuration (pattern tables, route maps, enum mappings) must have tests that exercise *every entry*, not a hand-picked subset. Include a count sanity check (assert that the number of entries is above a known floor) so an empty response doesn't silently pass.
22
22
  - **Undocumented assumptions are spec gaps, not test targets.** When the spec is silent on a behavior (e.g., can a user have multiple sessions? does logout delete or expire state?), that's a design decision to resolve — not something to test around. **Resolve the ambiguity first** (update the spec or get a decision), **then** write the test encoding the decided behavior. If you're writing tests before code (TDD), a spec gap blocks you — don't guess the intent and test your guess. If you discover the gap while reviewing existing code, file a spec issue and name the test to surface the question (e.g., `'login creates additional session token — multi-device support'` not `'login works'`).
23
23
 
24
24
  ---
@@ -50,14 +50,14 @@ expect(item.state).toBe('submitted'); // the effect
50
50
 
51
51
  **"Known failing" must be a bounded, tracked, VISIBLE state — never ambient noise.** A suite that runs chronically red carries zero signal: a genuine new failure hides among the tolerated ones. Quarantine-with-a-ticket converts "N flaky failures" into "N tracked exceptions", so any *unexpected* red is a real signal again.
52
52
 
53
- **Rule.** Every statically-skipped test (`describe.skip`, `it.skip`, `test.skip`, `xit`, `xdescribe`, `xtest`) must carry one of these markers, on the same line or within the two comment lines directly above it:
53
+ **Rule.** Every statically-skipped test — a skip written into the test source, rather than one decided at run time — must carry one of these markers, on the same line or within the two comment lines directly above it:
54
54
 
55
55
  ```
56
56
  // QUARANTINE: <issue-id> <reason> — known-failing, tracked by an issue.
57
57
  // SKIP-OK: <reason> — intentional non-failure skip (opt-in / env-gated / platform-specific).
58
58
  ```
59
59
 
60
- A skip carrying neither a ticket reference nor a `SKIP-OK:` reason is a convention violation.
60
+ A skip carrying neither a ticket reference nor a `SKIP-OK:` reason is a convention violation. The stack's testing skill lists the skip forms its runner has (`boop-typescript-testing` in a TypeScript project).
61
61
 
62
62
  **Enforcement.** A guard test scans every test file and fails when a skip lacks a marker, so it runs as part of the normal suite with no extra tooling. When you must quarantine, file (or reuse) a tracking issue first, then add the marker — don't skip silently.
63
63
 
@@ -67,9 +67,9 @@ A skip carrying neither a ticket reference nor a `SKIP-OK:` reason is a conventi
67
67
 
68
68
  **The rule.** A change runs the focused tests for what it touched, plus the project's whole-program static check. The full suite runs once per branch, on the work that completes it. The `boop-project-mgmt-beads-planning` skill carries the canonical wording to put on issues; this section is the reasoning behind it, and the two have to keep agreeing.
69
69
 
70
- **The command is `npm run typecheck`** — `tsc -p tsconfig.test.json`, which compiles `src/` and `tests/` as one program and emits nothing. `boop add testing` scaffolds the script and the config together, so in any project with this capability the static check has a name rather than an invocation each agent has to derive.
70
+ **The commands are `npm test` for the suite and `npm run typecheck` for the static check.** The project's stack capability supplies both as npm scripts — `typescript` in a TypeScript project — so every project has the same two names, and the static check has a name rather than an invocation each agent has to derive. A focused run names the test files or folders it runs; the stack's testing skill gives its exact form (`boop-typescript-testing` in a TypeScript project). Write that form on an issue, rather than leaving the claiming agent to derive it.
71
71
 
72
- If a project does not have it, add it before following the rule, and check the config before trusting it. A `tsconfig.test.json` that extends a build config and then includes `tests/**/*` inherits `"rootDir": "src"`, and every test file is then a TS6059 "not under rootDir" before a single assertion is read — a static check that fails on every file teaches an agent to skip it. The scaffolded config sets `"rootDir": "."`, `"noEmit": true` and an explicit `"types"` list for exactly that reason; its comments explain each one.
72
+ If a project has no `typecheck` script, add one before following the rule, and check that it passes on an unchanged tree before trusting it. A static check that fails on every file, because of a configuration mistake rather than a real error, teaches an agent to skip it.
73
73
 
74
74
  **Why the whole suite is the wrong tool mid-change.** The problem is localization, first and foremost. Break a shared dependency and the suite reports it as a cascade of failures in tests that never mention the thing you changed, and you are then reading a wall of red to reconstruct a fact the type-checker would have told you outright, naming a file and a line. A static check plus a search for callers answers "does everything that uses this still hold?" both faster and more precisely than the suite does.
75
75
 
@@ -83,13 +83,13 @@ Third, a suite already red for an unrelated reason gives you nothing to compare
83
83
 
84
84
  ### Go through the project's test script, not a bare runner invocation
85
85
 
86
- Run the suite the way the project defines it — `npm test` — rather than reaching past it to the runner binary. The script and the runner's config file together carry the settings the suite is tuned for: how many files run at once, and how much memory a worker may hold before the runner replaces it. A bare `npx jest` picks up the config file but drops anything that lives only in the script.
86
+ Run the suite the way the project defines it — `npm test` — rather than reaching past it to the runner binary. The script and the runner's config file together carry the settings the suite is tuned for: how many files run at once, and how much memory a worker may hold before the runner replaces it. Calling the runner directly picks up the config file but drops anything that lives only in the script.
87
87
 
88
- Those settings are not decoration. With Jest specifically, `--maxWorkers=1` **does nothing on its own** — Jest runs every file in its own main process, and never recycles it, whenever `maxWorkers <= 1`. Setting `workerIdleMemoryLimit` is what forces a real worker process and gives Jest a threshold at which to replace it; it is the load-bearing setting, and a long suite that grows its heap until it dies is what its absence looks like. Anyone "simplifying" the two settings down to the worker cap has restored the bug.
88
+ Those settings are not decoration, and the one that matters is not always the one that looks like it. The stack's testing skill says which setting does the work on its runner (`boop-typescript-testing` in a TypeScript project); read it before you "simplify" one away.
89
89
 
90
- Keep those settings in the runner's config file rather than in the `test` script, so that the focused runs this section asks for inherit them too. `boop add testing` scaffolds them that way, at four workers and a 500MB per-worker ceiling.
90
+ Keep those settings in the runner's config file rather than in the `test` script, so that the focused runs this section asks for inherit them too. The stack capability scaffolds them that way.
91
91
 
92
- **A worker pool makes test isolation a requirement, not a nicety.** More than one worker means test *files* run at the same time, in separate processes, against the same backing services. Anything two files share and do not namespace — a table one of them truncates in setup, a fixture directory, a well-known key or account id — is now a race, and it surfaces as a suite that passes alone and fails in a full run. That is the same symptom the shared-dev-database section below describes, reached by a different route, and it invites the same wrong fix of weakening the assertion that caught it. Namespace what each test creates. Where a suite is not isolation-safe yet, set `maxWorkers: 1`: that keeps the recycling, which is the part that fixes the memory, and gives up only the overlap.
92
+ **A worker pool makes test isolation a requirement, not a nicety.** More than one worker means test *files* run at the same time, in separate processes, against the same backing services. Anything two files share and do not namespace — a table one of them truncates in setup, a fixture directory, a well-known key or account id — is now a race, and it surfaces as a suite that passes alone and fails in a full run. That is the same symptom the shared-dev-database section below describes, reached by a different route, and it invites the same wrong fix of weakening the assertion that caught it. Namespace what each test creates. Where a suite is not isolation-safe yet, run one file at a time until it is, keeping whatever setting bounds each worker's memory.
93
93
 
94
94
  ## Integration tests never touch the shared dev database
95
95
 
@@ -180,7 +180,7 @@ When code is backed by a config table (patterns, routes, flags, enums):
180
180
  - Load the full config at test time from the real source
181
181
  - Iterate every entry and assert expected output
182
182
  - Collect all failures (don't fail-fast on first broken entry)
183
- - Sanity-check the count: `expect(records.length).toBeGreaterThan(N)`
183
+ - Sanity-check the count: assert there are more entries than a known floor, so an empty config fails
184
184
  - Skip only when each entry triggers expensive metered side effects; document why
185
185
 
186
186
  ---
@@ -191,18 +191,18 @@ When a bug is discovered through manual testing or UAT, follow this checklist:
191
191
 
192
192
  1. **Reproduce** — confirm the failure and understand the root cause.
193
193
  2. **Find the test gap** — look for an existing test that *should* have caught it. If one exists, understand why it didn't (usually: it mocked the boundary where the failure lives).
194
- 3. **Write a failing test first** — TDD applies to bug fixes, not just features. Write a test that reproduces the exact failure, confirm it fails for the right reason (Red), *then* apply the fix (Green). See [tdd-light.skill.md](test.tdd-light.skill.md).
194
+ 3. **Write a failing test first** — TDD applies to bug fixes, not just features. Write a test that reproduces the exact failure, confirm it fails for the right reason (Red), *then* apply the fix (Green). See [`boop-testing-tdd-light`](../boop-testing-tdd-light/SKILL.md).
195
195
  4. **Choose the right test level** — if the bug occurred at a system boundary (database, external API, serialization), the test must cross that boundary. A mock-based test cannot catch a boundary failure.
196
196
 
197
197
  ---
198
198
 
199
199
  ## Raw SQL and integration tests
200
200
 
201
- Any service function that uses `$queryRaw` or `$executeRaw` **must have an integration test that runs against the real database**. Mock-based tests for raw SQL provide false confidence — the mock accepts any query shape, but the real database may reject it (wrong column types, deserialization failures, missing columns).
201
+ Any service function that runs hand-written SQL **must have an integration test that runs against the real database**. Mock-based tests for raw SQL provide false confidence — the mock accepts any query shape, but the real database may reject it (wrong column types, deserialization failures, missing columns). Neither the type checker nor a mock sees the SQL inside a string; only a real query does.
202
202
 
203
- **Pattern**: insert fixture data via raw SQL or Prisma, call the function under test, assert the result, clean up in `afterAll`.
203
+ **Pattern**: insert fixture data, call the function under test, assert the result, and remove the fixture data when the suite finishes.
204
204
 
205
- **Especially important for `Unsupported` columns**: Prisma columns marked `Unsupported(...)` (e.g., pgvector `vector(1536)`) will cause runtime deserialization errors if selected in `$queryRaw`. TypeScript won't catch this, and mocks won't catch this. Only a real DB query will.
205
+ The data-access layer's skill gives the concrete form and the column types that make this most dangerous; for Prisma, that is `boop-database-prisma`.
206
206
 
207
207
  ---
208
208
 
@@ -222,12 +222,12 @@ When code produces a schema or structure that is validated by an external system
222
222
 
223
223
  | Subsystem | Skill |
224
224
  |---|---|
225
- | Express app (web & APIs) | [testing-express-web.skill.md](testing-express-web.skill.md) — Jest, HTTP route tests, CLI tools, test helpers |
226
- | Cloudflare edge workers | [testing-cf-workers.skill.md](testing-cf-workers.skill.md) — Worker unit/integration tests, wrangler dev setup |
225
+ | TypeScript toolchain | `boop-typescript-testing` (the `typescript` capability) — how `npm test` and `npm run typecheck` behave under Jest, ts-jest and `tsc`, focused-run forms, worker settings |
226
+ | Express app (web & APIs) | `boop-web-testing` (the `web` capability) — HTTP route tests, CLI tools, test helpers |
227
+ | Database (Prisma) | `boop-database-prisma` (the `database` capability) — integration tests for raw SQL, and the column types a mock cannot catch |
227
228
 
228
229
  ---
229
230
 
230
231
  ## References
231
232
 
232
- - [development.skill.md](development.skill.md) — Light TDD Flow, completion criteria
233
- - [jest.config.js](../../jest.config.js) — Jest configuration, at the project root this capability installs it to
233
+ - `boop-coding-development` — Light TDD Flow, completion criteria
@@ -1,33 +0,0 @@
1
- /** @type {import('ts-jest').JestConfigWithTsJest} */
2
- module.exports = {
3
- preset: 'ts-jest',
4
- testEnvironment: 'node',
5
- testMatch: ['<rootDir>/tests/**/*.test.ts'],
6
- transform: {
7
- '^.+\\.ts$': ['ts-jest', { tsconfig: 'tsconfig.test.json' }],
8
- },
9
-
10
- // Run test files across a small pool of worker processes that Jest recycles.
11
- //
12
- // `workerIdleMemoryLimit` is the load-bearing setting and it is easy to drop
13
- // by accident. Without it Jest runs every file in its own main process — never
14
- // recycling it — whenever `maxWorkers <= 1`, and that process's heap grows for
15
- // the whole run until a long suite dies of it. Setting a memory limit is what
16
- // overrides that decision: it forces real workers and gives Jest a threshold at
17
- // which to replace one. `maxWorkers` on its own does not do this, and neither
18
- // did the `--runInBand` this replaced — that flag asks for exactly the
19
- // un-recycled main process described above.
20
- //
21
- // The limit is compared against V8 `heapUsed`, not RSS, and is checked between
22
- // test files. Budget for real memory use somewhat above `maxWorkers` times this
23
- // number, and note that a single file exceeding it is not caught mid-flight.
24
- //
25
- // Four workers is a deliberate middle setting; six is comfortable on a developer
26
- // machine with memory to spare. The constraint on raising it is not the machine,
27
- // it is your tests: files now run CONCURRENTLY, so anything sharing a database,
28
- // a fixture directory, or a well-known key with another test file will collide.
29
- // Namespace what each test creates. Drop to `maxWorkers: 1` if a suite is not
30
- // isolation-safe yet — that still gets you the recycling, just without overlap.
31
- maxWorkers: 4,
32
- workerIdleMemoryLimit: '500MB',
33
- };
@@ -1,17 +0,0 @@
1
- import app from '../src/app';
2
-
3
- describe('app', () => {
4
- it('exports a valid Express application', () => {
5
- expect(app).toBeDefined();
6
- expect(typeof app.get).toBe('function');
7
- expect(typeof app.post).toBe('function');
8
- expect(typeof app.use).toBe('function');
9
- });
10
-
11
- it('has JSON and URL-encoded body parsing enabled', () => {
12
- const stack = (app as any).router?.stack ?? (app as any)._router?.stack ?? [];
13
- const layerNames = stack.map((layer: any) => layer.name);
14
- expect(layerNames).toContain('jsonParser');
15
- expect(layerNames).toContain('urlencodedParser');
16
- });
17
- });
@@ -1,26 +0,0 @@
1
- {
2
- // The config ts-jest type-checks against, and the one to point a static
3
- // check at: `npx tsc -p tsconfig.test.json`.
4
- "extends": "./tsconfig.json",
5
- "compilerOptions": {
6
- // The base config sets "rootDir": "src" so a build emits dist/ cleanly.
7
- // Tests live outside src/, so inheriting that root makes every one of them
8
- // a TS6059 "not under rootDir" error before a single assertion is read.
9
- // Widening the root to the project is what lets tests and sources share a
10
- // program; nothing is emitted from here, so the narrower build root is
11
- // unaffected.
12
- "rootDir": ".",
13
- // Type-check only. Without this a stray `tsc -p tsconfig.test.json` would
14
- // emit compiled tests into dist/ alongside the real build output.
15
- "noEmit": true,
16
- // Name the type packages explicitly instead of relying on TypeScript
17
- // pulling in everything under node_modules/@types. That automatic
18
- // inclusion is not dependable across TypeScript majors — under 6.x it
19
- // stops finding @types/jest here, and every `describe`/`it`/`expect` in
20
- // the suite becomes TS2593/TS2304 while the package sits installed and
21
- // correct. Listing them costs nothing and does not change on upgrade.
22
- "types": ["jest", "node"]
23
- },
24
- "include": ["src/**/*", "tests/**/*"],
25
- "exclude": ["node_modules", "dist"]
26
- }
@@ -1,94 +0,0 @@
1
- ---
2
- name: boop-testing-testing-express-web
3
- description: "When you need to write or run tests for the Express server — services, controllers, middleware, and HTTP routes."
4
- metadata:
5
- category: test
6
- ---
7
- > **Read `PROJECT.md`, beside this file, first.**
8
- > It records how this project applies the guidance below, and where it deliberately differs.
9
- > Where the two conflict, `PROJECT.md` wins.
10
- > Record project-specific rules there, never in this file.
11
-
12
- # Testing: Express App (Web & APIs)
13
-
14
-
15
- Parent skill: [`boop-testing-testing`](../boop-testing-testing/SKILL.md) (philosophy, generic approach)
16
-
17
- ---
18
-
19
- ## Running Tests
20
-
21
- ```bash
22
- npm test # Full suite
23
- npx jest tests/services/foo.test.ts --no-coverage # Single file
24
- npx jest tests/services/foo --no-coverage # Pattern match
25
- npx jest tests/services/foo.test.ts -t "handles replay" # By test name
26
- ```
27
-
28
- The focused forms are safe to reach for: the concurrency and per-worker memory settings live in `jest.config.js`, not in the `test` script, so `npx jest <path>` runs under the same settings the full suite does. Which of these you should be running on a given piece of work is a separate question — see "Which tests to run, and when" in the parent skill.
29
-
30
- **Prerequisites**: integration tests run against the project's real backing services rather than mocks, so whatever the app talks to at runtime — the database first of all — has to be reachable from the machine running the tests. Which services those are depends on the capabilities the project has installed; its README and environment template are the authority.
31
-
32
- ## Test File Layout
33
-
34
- ```
35
- tests/
36
- helpers/ # shared setup, factories, harnesses (see below)
37
- services/
38
- foo.test.ts # Unit tests (mocked deps)
39
- foo.integration.test.ts # Integration tests (real services)
40
- controllers/
41
- itemsAPI.test.ts # HTTP route tests
42
- middleware/
43
- utils/
44
- ```
45
-
46
- - `.test.ts` for Jest auto-discovery. Keep the mocked tests and the real-service tests for the same subject in **separate files**: `jest.mock` is hoisted above the imports and applies to every test in the module, so one file cannot both stub a dependency and exercise the real one. `.integration.test.ts` is the conventional name for the half that uses the real services.
47
- - Group by domain: `services/`, `controllers/`, `middleware/`
48
-
49
- ## Test Helpers
50
-
51
- Shared test setup belongs in one place under `tests/`, not re-derived in each spec file: booting the app, creating the entities a test needs and tearing them down again, and harnesses that make an awkward dependency — a broker, a queue, a rendered template — drivable from a test.
52
-
53
- **Read what the project already has before you write one.** These helpers are not scaffolded and cannot be, because they follow whichever capabilities and domain the project has. Every project grows its own set, and the shape varies from one shared module to a directory of purpose-built ones. Reuse what fits and extend it, rather than starting a private copy inside your test file. A helper used by exactly one test is not a helper — leave it in the test.
54
-
55
- When you do add one, keep two properties. A factory returns a real, persisted entity rather than a hand-built object literal, so the test exercises the same shape production does. And whatever creates data owns its teardown, so one failed run does not poison the next.
56
-
57
- ## Integration Test Data Isolation
58
-
59
- Integration tests share their backing services with every other integration test, and with whatever the last run left behind. Three habits keep them from interfering with each other:
60
-
61
- - **Namespace whatever you create.** A per-run prefix (`_test/${Date.now()}`, a UUID) keeps parallel runs and repeat runs from colliding on unique constraints.
62
- - **Track created records and remove them in `afterAll`**, each cleanup wrapped in its own `try/catch` so one failure doesn't strand the rest.
63
- - **Scope instead of deleting where deletes are expensive.** Some stores — append-only logs, analytics columnstores, anything with asynchronous merge semantics — make row deletion slow or eventually-consistent. Isolate those by writing under a test-specific tenant or key and leave the rows in place.
64
-
65
- ## HTTP Route Tests
66
- Test Express routes via **real HTTP requests** (`fetch()`) against a running local server. This exercises the full middleware chain (auth → controller → service → DB → response).
67
-
68
- Why real HTTP over supertest: supertest skips TCP/listen and can mask middleware ordering or config bugs. Auth middleware is a critical path worth testing through, not around.
69
-
70
- **Setup:**
71
- ```typescript
72
- const BASE_URL = process.env.TEST_BASE_URL || 'http://localhost:3000';
73
- const TEST_API_KEY = process.env.TEST_API_KEY; // however this project issues API keys
74
- const authHeaders = { 'Authorization': `Bearer ${TEST_API_KEY}`, 'Content-Type': 'application/json' };
75
- ```
76
-
77
- **What to cover:** auth rejection (401/403), request validation (400), the happy-path response shape, and the authorization boundary — that one account cannot reach another's data. If the project gates routes on anything further (feature flags, roles, plan tiers), cover the closed gate too, and assert the status code it actually returns rather than the one that seems right.
78
-
79
- **Prereqs:** a local server running against a reachable database, a durable test account, and whatever credential the auth middleware expects (`TEST_API_KEY` by convention).
80
-
81
- ## Ad-Hoc Testing Tools
82
-
83
- Jest is what runs in CI and before a commit. Alongside it, projects grow small CLIs for the things a test suite is the wrong shape for: generating fixture data, poking a live local server, checking what actually landed in the database.
84
-
85
- Boop scaffolds one — `npm run db:query` (database capability) — for reading the local database without opening Prisma Studio. Anything beyond that is project-grown; `package.json` scripts and `src/scripts/` are where to look for what yours has, and `--help` on each is faster than reading its source.
86
-
87
- These are exploratory aids. They are not part of the automated suite, and none of them substitutes for a Jest test that can go red on its own.
88
-
89
- ## References
90
-
91
- - [`boop-testing-testing`](../boop-testing-testing/SKILL.md) — testing philosophy and the generic approach
92
- - [`boop-testing-tdd-light`](../boop-testing-tdd-light/SKILL.md) — the red/green loop for a single change
93
- - The project's own shared test setup under `tests/` — read it before adding another helper
94
- - `jest.config.js` at the project root — test discovery, transform, and match config