Skip to content

Commit 28955b2

Browse files
tyler-daneclaude
andauthored
test: migrate backend and scripts to bun test runner (#2184)
* test: migrate backend and scripts to bun test runner Retire Jest for the backend and scripts suites in favor of Bun's runner, driven by a per-file-process launcher over one shared in-memory MongoDB replica set. Restores Jest-like per-file isolation without per-file startup. - 3-run medians: backend 43.5s -> 11s (-74%), scripts 52.4s -> 14.7s (-72%). - Remove jest, @shelf/jest-mongodb, jest-environment-node, @babel/*, and their config; add mongodb-memory-server. @types/jest kept (type annotations only). - Retire the six permanently-completed 2025 migration integration suites; add a fast discovery/ordering guard and a migration baseline README. All migration implementations and transforms are retained (still used by provisioning/web). - Mongo test lifecycle: no production backoff under test env, one shared server, cleanupTestDb no longer disconnects. - Bun compat fixes: bun:test imports (never jest), requireActual snapshot to stop mutate-after-mock recursion, lazy mockRejectedValue, stable-per-name winston mock, mutable compassTestState, .resolves.toBeUndefined(). - Test tiers (fast/db/migrations, auto-classified) and CI mongod binary cache. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(scripts): tighten migration-discovery guard and runner teardown - Drop a tautological assertion in migration-discovery.test.ts that compared a lexicographic sort to itself (always true); keep the meaningful timestamp-prefix and name-uniqueness checks that actually guard Umzug's order. - Wrap the launcher's run in try/finally so the in-memory mongod is always stopped even if a worker throws. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(scripts): declare @sinonjs/fake-timers for the bun compat shim The jest->bun compat shim requires @sinonjs/fake-timers, previously satisfied transitively via jest. Removing jest orphaned it, so a frozen-lockfile install in CI could no longer resolve it and every unit job that loads the shim (core, web, backend, scripts) failed. Declare it explicitly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(backend): import lifecycle hooks in preload helpers from bun:test backend.test.start and mock.setup registered global beforeEach/afterAll via Bun's injected test globals. From a preloaded (dynamically imported) module that injection is racy -- one CI run intermittently failed with "afterEach is not defined" while the same commit passed elsewhere and always passed locally. Import the hooks explicitly so registration never depends on injection timing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(backend): apply bun test conversions to merged event.service tests Main's cross-calendar-move suite (#2183) landed after this branch forked. Apply the same bun:test conversions to it: import afterEach explicitly (Bun disables injected globals once a file imports from bun:test) and make the rejected mock lazy so Bun doesn't flag an eager unhandled rejection. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 67d94c8 commit 28955b2

114 files changed

Lines changed: 861 additions & 2177 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test-unit.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,15 @@ jobs:
117117
run: |
118118
bun install --frozen-lockfile
119119
120+
# backend and scripts boot an in-memory MongoDB; cache the downloaded
121+
# mongod binary so each run doesn't re-fetch it.
122+
- name: Cache MongoDB binaries
123+
if: matrix.project == 'backend' || matrix.project == 'scripts'
124+
uses: actions/cache@v5
125+
with:
126+
path: ~/.cache/mongodb-binaries
127+
key: ${{ runner.os }}-mongodb-memory-server
128+
120129
- name: Run web tests
121130
if: matrix.project == 'web'
122131
timeout-minutes: 1
@@ -127,6 +136,7 @@ jobs:
127136
128137
- name: Run ${{ matrix.project }} tests
129138
if: matrix.project != 'web'
139+
timeout-minutes: 5
130140
env:
131141
TZ: Etc/UTC
132142
run: |

babel.config.js

Lines changed: 0 additions & 7 deletions
This file was deleted.

bun.lock

Lines changed: 44 additions & 584 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/development/feature-file-map.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ Use this document to find the first files to inspect for common Compass changes.
104104

105105
## Test Anchors
106106

107-
- Retained Jest project config for `web`, `backend`, and `scripts`: `jest.config.js`
107+
- Mongo-backed test launcher (per-file isolation over one in-memory replica set): `packages/scripts/src/testing/run-tests.ts`
108+
- Bun/Jest compatibility shim used by every package's tests: `packages/scripts/src/testing/apply-bun-jest-compat.cjs`
108109
- Core test setup: `packages/core/src/__tests__`
109110
- Web test setup: `packages/web/src/__tests__`
110111
- Web mock server handlers: `packages/web/src/__tests__/__mocks__/server/mock.handlers.ts`

docs/development/performance-baselines.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,10 @@ never pay for it.
1717

1818
```bash
1919
# normal run -- confirms the bench suite shows as skipped
20-
TZ=UTC ./node_modules/.bin/jest --selectProjects backend
20+
bun run test:backend
2121

2222
# the real run -- prints one `[bench]` summary line per scenario.
23-
# (--testPathPattern is load-bearing: a bare trailing "bench" arg is
24-
# consumed by --selectProjects as a nonexistent project name and the
25-
# whole suite runs instead.)
26-
RUN_BENCH=1 TZ=UTC ./node_modules/.bin/jest --selectProjects backend --testPathPattern="bench"
23+
RUN_BENCH=1 bun packages/scripts/src/testing/run-tests.ts backend --filter bench
2724
```
2825

2926
### Last recorded numbers

docs/development/testing-playbook.md

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,24 +44,20 @@ E2E workflow (`test-e2e.yml`) is separate and runs on pull requests to `main` vi
4444

4545
## Current Test Strategy
4646

47+
Every package now runs on Bun's test runner; Jest has been removed.
48+
4749
- `bun run test:core` uses `bun test` with a small compatibility preload for the core BSON mock setup.
4850
- `bun run test:web` runs `bun test --cwd packages/web` directly. Web tests should be isolated enough to run in one Bun process without batching.
49-
- `bun run test:backend` and `bun run test:scripts` intentionally retain the existing Jest harness while their hoist-heavy module-mocking patterns are migrated.
50-
51-
## Retained Jest Layout
52-
53-
Source:
54-
55-
- `jest.config.js`
56-
57-
Projects:
51+
- `bun run test:backend` and `bun run test:scripts` use `packages/scripts/src/testing/run-tests.ts`, a launcher that boots one in-memory MongoDB replica set and runs each test file in its own `bun test` process (Jest-like per-file isolation) against the shared server, in parallel. This keeps the isolation Jest gave us without its per-file startup cost.
52+
- Test files import lifecycle/assertion APIs from `bun:test` (never `jest` -- the ambient `jest` global is a compatibility shim from `packages/scripts/src/testing/apply-bun-jest-compat.cjs` that maps `jest.mock`/`jest.requireActual`/etc. onto `bun:test`).
5853

59-
- `core`
60-
- `web`
61-
- `backend`
62-
- `scripts`
54+
### Test tiers
6355

64-
Each project has its own setup files and module alias mapping.
56+
- `bun run test:backend` / `bun run test:scripts` -- the full package suite.
57+
- `bun run test:backend:fast` / `bun run test:scripts:fast` -- only files that never touch Mongo (auto-classified: no `setupTestDb`).
58+
- `bun run test:backend:db` / `bun run test:scripts:db` -- only the Mongo-backed files.
59+
- `bun run test:migrations` -- the active migration suites under `packages/scripts/src/migrations`.
60+
- Focus a run with `--filter <substring>`, e.g. `bun packages/scripts/src/testing/run-tests.ts backend --filter user.controller`.
6561

6662
## What To Run By Change Type
6763

@@ -261,7 +257,7 @@ This keeps tests on production code paths while avoiding brittle layout coupling
261257

262258
### Jest Unbound-Method Rule In Tests
263259

264-
Test linting enforces `jest/unbound-method`. If you need to assert method calls on non-mock objects, spy on the method first so assertions are bound to a Jest mock/spy.
260+
If you need to assert method calls on non-mock objects, spy on the method first (`jest.spyOn(...)`, backed by `bun:test`) so the assertion is bound to a real mock/spy rather than an unbound method reference.
265261

266262
Useful anchors:
267263

@@ -279,7 +275,7 @@ Preferred style:
279275

280276
**Do not import `mongoService` (or other persistence implementations) directly in tests.** Use test drivers instead (e.g. `UserDriver`, `GoogleWatchDriver` in `packages/backend/src/__tests__/drivers/`). Drivers encapsulate persistence so that switching away from Mongo (or another store) in the future does not require changing test code.
281277

282-
CI runs every lane with `TZ: Etc/UTC` (see CI Unit Test Workflow above). If a backend test only fails locally, match that explicitly rather than relying on your machine's default timezone: `TZ=UTC ./node_modules/.bin/jest --selectProjects backend`.
278+
CI runs every lane with `TZ: Etc/UTC` (see CI Unit Test Workflow above). The launcher already sets `TZ=Etc/UTC` for each test process, so a backend test that only fails locally is rarely a timezone issue; to reproduce CI exactly, run `bun run test:backend`.
283279

284280
Useful anchors:
285281

jest-mongodb-config.js

Lines changed: 0 additions & 16 deletions
This file was deleted.

0 commit comments

Comments
 (0)