Skip to main content
Amends 0022 (decision 4: shared fixtures and doubles live in @stackra/testing, and domain helpers in each owning package’s ./testing entry) and 0126 (decision 2: a named exception for the ./e2e and ./contract entries of @stackra/testing only).

Context

Every package and service needs the same test plumbing: a vitest preset, factories, fakes for the clock and ids, and matchers. Before this decision each package wrote its own, and the domain helpers (a module under test, a dispatch runner, actors, refusal and event matchers, a permission matcher) had no agreed home. 0022 decision 4 asked for “an explicit testing entry of the package that owns the contract”, but did not say what a shared package holds. 0182 fixed the test tiers by filename infix, so the entries below line up with those tiers. The owner settled the placement on 2026-10-21. That answer reverses an earlier answer of the same day, which put every helper in one package.

Decision

  1. @stackra/testing is a tier:lib package in the developer category (packages/developer/testing, 0127). It has six entries: ., ./node, ./react, ./native, ./e2e and ./contract. Each entry is declared in package.json only when it holds real code, as 0126 decision 2 says.
  2. The . entry holds only generic code. It re-exports vitest, and it provides generic factories, fakes for the clock and ids, and generic matchers. It has no workspace dependency and no domain type. Nothing in . knows what a module, an action, a permission or an event is.
  3. Domain helpers live in the owning package’s ./testing entry, built on @stackra/testing. The helpers of this class are createTestingModule, run, the actor helpers, toRefuseWith, toEmit, toHavePermission and toMatchManifest. Each one lives in the ./testing entry of the package that owns the thing it tests. A package never reaches into another package’s ./testing entry.
  4. Mock hoisting is configured in the preset. The shared vitest preset (packages/tooling/vitest/src/preset.ts) is set so that vi.mock calls in a test file hoist when the file imports from @stackra/testing. A test in @stackra/testing proves this. If that test fails, the work stops and returns to the owner. There is no fallback to global vi (globals: true).
  5. The entry names are fixed here, and the tier they serve is not. ./contract and ./e2e are named for the .contract and .e2e tiers of 0182. Which kind of test uses which entry is set in the test-by-kind table (standards/testing-by-kind), and the contents of each entry are set by the package build.
  6. Named exception to 0126 decision 2. Decision 2 of 0126 lists the entries a package may have: ., ./contracts, ./node, ./react, ./native, ./testing and ./devtools. For @stackra/testing alone, the entries ./e2e and ./contract are added to that list, with these names. Nothing else in the entry list changes, and no other package gets these entries. @stackra/testing/contract is a subpath of the testing package; it is unrelated to a member’s ./contracts entry (the vocabulary entry of 0117), and the two names do not mean the same thing.

Consequences

  • A package’s tests import its domain helpers from its own ./testing entry, so a helper is never copied between packages.
  • @stackra/testing stays free of domain dependencies, so it can sit at the bottom of the dependency graph for tests.
  • Each package that owns a contract grows a ./testing entry when it first needs one. Until then it has none.
  • The mock-hoisting test is a gate: if the preset changes and hoisting breaks, the change fails before any package relies on it.

Enforcement

planned — the hoisting test in packages/developer/testing/src/__tests__/ (built in Phase T, item T1) proves decision 4. The exports of each package and the ./testing placement are checked by checkEntriesMatchExports in tools/scripts/validate-repository.mjs once the structure work (STA-220) lands. Nothing checks decision 3 today.

Supersedes and sources

Supersedes: none. Amends: 0022 decision 4; 0126 decision 2 (named exception, decision 6 above). Sources: owner answers on testing helpers, 2026-10-21 (the reversal of the earlier answer of the same day); 0126 decision 2 (entries).