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.
| Where | What runs | How |
|---|---|---|
| Locally | Every suite, from the source | pnpm test, and the other source runs |
| CI | format:check, lint and typecheck, on the source | one job of checks |
| CI | Every suite, on what a build job built (TEST_FROM=dist), with coverage | a 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.
| Suite | From the source | From the build |
|---|---|---|
| Unit | test | test:dist |
| Browser | test:browser | test:browser:dist |
| Integration | test:integration | test: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
pnpm test # the unit suite, from the sourcepnpm test --project @scope/lib # one package's testspnpm run test:browser # the browser suite, from the sourcepnpm run test:integration # the integration suite, from the source
pnpm run build && pnpm run test:dist --coverage # what CI's unit test job runspnpm run build && pnpm run test:browser:dist --coveragepnpm run build && pnpm run test:integration:dist --coverageNever 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.
