@boopkit/cap-testing 0.5.8 → 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 +10 -52
- package/capability.json +14 -29
- package/package.json +2 -4
- package/skills/boop-testing-tdd-light/SKILL.md +2 -2
- package/skills/boop-testing-testing/SKILL.md +16 -15
- package/scaffold/jest.config.js +0 -33
- package/scaffold/tests/app.test.ts +0 -17
- package/scaffold/tsconfig.test.json +0 -26
- package/skills/boop-testing-testing-express-web/SKILL.md +0 -94
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`
|
|
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
|
-
- `
|
|
34
|
-
- `
|
|
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
|
-
|
|
16
|
+
Express-specific test guidance ships with `cap-web` as `boop-web-testing`.
|
|
42
17
|
|
|
43
|
-
|
|
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
|
-
|
|
20
|
+
**Other capabilities:** none.
|
|
53
21
|
|
|
54
|
-
|
|
22
|
+
**npm dependencies:** none.
|
|
55
23
|
|
|
56
|
-
|
|
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
|
-
|
|
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`
|
|
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": "
|
|
3
|
+
"description": "Testing method: the testing principles and the TDD-Lite workflow",
|
|
4
4
|
"type": "development",
|
|
5
5
|
"dependencies": [],
|
|
6
|
-
"devDependencies": [
|
|
7
|
-
|
|
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.
|
|
4
|
-
"description": "
|
|
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, `
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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,
|
|
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:
|
|
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
|
---
|
|
@@ -198,11 +198,11 @@ When a bug is discovered through manual testing or UAT, follow this checklist:
|
|
|
198
198
|
|
|
199
199
|
## Raw SQL and integration tests
|
|
200
200
|
|
|
201
|
-
Any service function that
|
|
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
|
|
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
|
-
|
|
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,11 +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
|
-
|
|
|
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 |
|
|
226
228
|
|
|
227
229
|
---
|
|
228
230
|
|
|
229
231
|
## References
|
|
230
232
|
|
|
231
233
|
- `boop-coding-development` — Light TDD Flow, completion criteria
|
|
232
|
-
- [jest.config.js](../../jest.config.js) — Jest configuration, at the project root this capability installs it to
|
package/scaffold/jest.config.js
DELETED
|
@@ -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
|