Fixtures and CI

Fixtures, Coverage and Continuous Integration

A suite is trustworthy only when each test starts from a known state. Make the fixture a factory — makeStore(rows = seed) copies its seed rows into a fresh Map and returns the store methods over it — and call it in beforeEach. A test needing an empty catalog writes createApp({ store: makeStore([]) }); one needing a broken dependency passes { get: async () => { throw new Error('down'); } } to check for a 500. Coverage then comes from the runner: --experimental-test-coverage prints a per-file table, and --test-coverage-lines, --test-coverage-branches and --test-coverage-functions turn a shortfall into a non-zero exit code.

Output of 58
$ node --test --experimental-test-coverage          # 6 tests, all passing
ℹ file                 | line % | branch % | funcs % | uncovered lines
ℹ  app.js              |  95.00 |   100.00 |   75.00 | 17
ℹ  books-controller.js |  84.00 |    50.00 |   80.00 | 9-10 20-21
ℹ  store.js            |  95.00 |   100.00 |   80.00 | 11
ℹ all files            |  90.77 |    72.73 |   78.57 |
$ node --test --experimental-test-coverage --test-coverage-lines=95    # exit code 1
ℹ Error: 90.77% line coverage does not meet threshold of 95%.

The uncovered lines name themselves: line 17 of app.js is the error handler no test makes fire, 9-10 and 20-21 of the controller the list and create paths no unit test calls. Read the table for which lines are untested, not as a score.

.github/workflows/ci.yml — the suite on every pushYAML
name: CI
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix: { node: [22, 24] }
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with: { node-version: '${{ matrix.node }}', cache: npm }
      - run: npm ci
      - run: node --test --experimental-test-coverage --test-coverage-lines=85

npm 2,036 ci installs exactly what package-lock.json pins and fails on a stale lock file; npm install would quietly resolve new versions and make the run unreproducible. For a larger project, jest 30.5.1 and vitest 5.0.1 add module mocking, snapshots and parallel workers — and both drive supertest 14,405 unchanged.