Skip to content

Testing

This guide shows you where a workspace’s tests run: from the source on your machine, with nothing built, and from the build in CI, with coverage. It lists the pair of scripts each suite needs, and the commands you run locally.

Source locally, build in CI

You run the tests from the source. They start at once, with nothing built, and an edit shows up in the next run. The resolve hook and Vite’s aliases read each workspace package from its TypeScript, as the tests guide shows.

CI runs the tests from the build. What you publish is what the build wrote, so that is what CI tests: each package through its export map to its dist, as a consumer loads it. A test of the build finds what a test of the source cannot, such as an export that points at no file, a module the build left out, or code that only works before it is compiled.

On the source, CI runs only the static checks, which read the files and build nothing: the format check, the lint and the typecheck.

WhereWhat runsHow
LocallyEvery suite, from the sourcepnpm test, and the other source runs
CIformat:check, lint and typecheck, on the sourceone job of checks
CIEvery suite, on what a build job built (TEST_FROM=dist), with coveragea test job of each suite, after the build

The script pairs

Each suite a repo has takes two scripts in the root’s package.json: one that runs it from the source, and one that runs it from the build with the same arguments. You run the first; CI runs the second.

SuiteFrom the sourceFrom the build
Unittesttest:dist
Browsertest:browsertest:browser:dist
Integrationtest:integrationtest:integration:dist

The source script gives Node the resolve hook by --import. The build script sets TEST_FROM=dist and gives no hook. Both load the Vitest config with Node (--configLoader native):

{
"scripts": {
"test": "NODE_OPTIONS=\"$NODE_OPTIONS --import=@wtf/project-resolve/register\" vitest run --configLoader native",
"test:dist": "TEST_FROM=dist vitest run --configLoader native",
"test:browser": "NODE_OPTIONS=\"$NODE_OPTIONS --import=@wtf/project-resolve/register\" vitest run --configLoader native --config vitest.browser.config.ts",
"test:browser:dist": "TEST_FROM=dist vitest run --configLoader native --config vitest.browser.config.ts",
"test:integration": "NODE_OPTIONS=\"$NODE_OPTIONS --import=@wtf/project-resolve/register\" vitest run --configLoader native --config vitest.integration.config.ts",
"test:integration:dist": "TEST_FROM=dist vitest run --configLoader native --config vitest.integration.config.ts"
}
}

Leave out the pairs of the suites you don’t have. A repo with no browser tests has no test:browser and no test:browser:dist.

Coverage from the source maps

--coverage on a run of the build still reports the sources. Each file in dist has its source map beside it, and Vitest’s v8 provider maps each line it ran back to packages/*/src. A module no test loads counts as 0%. So the coverage CI reports is the coverage of the code you wrote, measured by running the code you publish.

In CI, pass --coverage to each suite’s script of the build: pnpm run test:dist --coverage, pnpm run test:browser:dist --coverage and pnpm run test:integration:dist --coverage. With CI set, the Vitest configs also write a JUnit report and a Cobertura report into reports/, which a test job hands to GitLab: the unit suite’s reports/test-results.xml, the browser suite’s reports/browser-results.xml, the integration suite’s reports/integration-results.xml, and reports/cobertura-coverage.xml.

Run them locally

Terminal window
pnpm test # the unit suite, from the source
pnpm test --project @scope/lib # one package's tests
pnpm run test:browser # the browser suite, from the source
pnpm run test:integration # the integration suite, from the source
pnpm run build && pnpm run test:dist --coverage # what CI's unit test job runs
pnpm run build && pnpm run test:browser:dist --coverage
pnpm run build && pnpm run test:integration:dist --coverage

Never build to make a source run pass. If a source run fails until you build, something reads a package’s dist, and that is the bug to fix. Run the build’s suites before you push when a change touches what the build writes, such as an export map, a files list or the build config.