Repository navigation
fix(vitest): run app tests without starting the Worker - #762
Merged
Merged
Conversation
`vp test` stopped with a startup error before running any test in Void
apps on the default Cloudflare target, including a fresh
`npm create void` app:
ReferenceError: module is not defined
With `nodejs_compat`, which Void turns on for auth, PostgreSQL, MySQL,
Sandbox, and TanStack Start, React Router and vinext apps:
Error: AssertionError: depsOptimizer is required in dev mode
Vitest loads `vite.config.ts` and starts a Vite server of its own, so
the Cloudflare Vite plugin that `voidPlugin()` includes started the
Worker there too. Vitest turns off dependency prebundling in nearly
every environment, the Worker's included, and the Cloudflare plugin
relies on it: a CommonJS dependency such as React reached workerd
unconverted, and the plugin's own nodejs_compat polyfill import
asserted on the missing optimizer. Users could not remove the plugin,
and `test.deps.optimizer` does not bring discovery back.
## Fix
`isVitestServer()` recognizes the server that Vitest creates by the
plugins Vitest adds to it (`vitest`, `vitest:*`), in any `--mode`. A
server that a test starts through Vite's API, and `vp dev --mode test`,
are not Vitest's and keep everything.
In Vitest's server:
- The plugins of Void's Cloudflare wrapper, the Cloudflare Vite plugin
included, do not apply, in Void apps and in framework apps. Void's
other plugins still resolve `void/*`, `@schema` and the generated
modules, so tests can import route handlers and page loaders.
- `migrationPlugin` does not apply, so a test run no longer migrates
the local D1 database or the PostgreSQL or MySQL database in `.env`.
- The AI project setup prints its hint instead of prompting.
Tests run in Node.js, so a call that reaches a binding still throws
`env: Cloudflare env is unavailable`; `vi.mock()` can replace the
binding module.
## Tests
- Unit: the detection, the Cloudflare wrapper and the migration plugin
with and without Vitest's plugins (modes `test` and `staging`), and
the AI setup prompt.
- Integration: `vp test` in isolated copies of the React Pages
playground (as is, with `nodejs_compat` and with `--mode staging`)
and the TanStack Start playground.
- Packed consumer: Vite+ fixtures run `vp test` with a mocked binding.
## Docs
Add a Testing guide: `vp test` needs no configuration, tests run in
Node.js without bindings, and test files stay out of the convention
directories. Link it from the email testing sections and the Void
skill, and note the Vitest server in the package AGENTS.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Testing guide imports test APIs from `vite-plus/test`, the Vite+ toolchain's entry for Vitest, and names `vitest` for the Vite toolchain. The email testing examples still imported from `vitest`, which TypeScript cannot resolve in a pnpm-installed Vite+ app that does not install Vitest directly. Import from `vite-plus/test` there too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The unit test that starts Vite dev servers to check where migrations apply needs no file watching. Pass `watch: null`, as the other unit tests that start a dev server do, so no native file watcher runs in the unit project's worker threads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cpojer
self-requested a review
November 9, 2026 22:35
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
vp teststops with a startup error before it runs any test in a Void app on the default Cloudflare target. A fresh app shows it with a singleexpect(1).toBe(1)test:With
nodejs_compat, which Void also turns on for apps that use auth, PostgreSQL, MySQL or Sandbox, and for TanStack Start, React Router and vinext apps:vp devworks. Vitest loadsvite.config.tsand starts a Vite server of its own, so the Cloudflare Vite plugin thatvoidPlugin()includes starts the Worker there too. Vitest turns off dependency prebundling in nearly every environment of that server, the Worker's included, and the Cloudflare plugin relies on it in development: a CommonJS dependency such as React reaches workerd unconverted, and withnodejs_compatthe plugin's own polyfill import asserts on the missing optimizer. Users can't remove the plugin, andtest.deps.optimizerdoesn't bring dependency discovery back. The only workaround was avitest.config.tswithoutvoidPlugin(), which loses@schemaand Void's generated modules, so the starter'spages/index.server.tscan't be imported.Vitest's server also ran dev-server work. Each
vp testapplied local migrations, for PostgreSQL and MySQL to the database in.env, and in an app that uses AI without a linked project it would start the interactive project setup in a terminal.Fix
isVitestServer()recognizes the server that Vitest creates by the plugins Vitest adds to it (vitest:*). In that server:void/*,@schemaand the generated modules, so tests can import route handlers and page loaders.migrationPlugindoesn't apply.Code generation, types and warnings stay as they are. Tests run in Node.js, so a call that reaches a binding still throws
env: Cloudflare env is unavailable. Running tests in workerd with real bindings, as@cloudflare/vitest-plugindoes for plain Workers, would need Void's generated entry, inferred bindings and migrations in the test runtime, and is left for later.Vite passes each plugin's
applythe config of the server it resolves, Vitest's plugins included, so the check works in any--modeand only for Vitest's own server. The alternatives fail in worse directions:process.env.VITESTcreateVitest()doesn't set itmode === 'test'vitest --mode stagingvp dev --mode test, which would lose the WorkerThe check also accepts the plugin named
vitestthat Vitest 3.2.4 and 4.0.0 add. The integration test below fails if a later Vitest renames these plugins, andpackages/void/AGENTS.mdasks to check the names when updating Vitest.Docs
vp testneeds no configuration, test APIs come fromvite-plus/test(vitestwith the Vite toolchain), test files stay out of convention directories (a test file inroutes/becomes a route), andvi.mock()replaces a binding module, since bindings aren't available in tests.vite-plus/testand link to the guide.vp testcheck.Tests
isVitestServer(); the Cloudflare wrapper resolved with and without Vitest's plugins, in modestestandstaging;migrationPluginin real dev servers; and the AI setup prompt.packages/void/test/integration/vitest-app.test.tsrunsvp test runin isolated copies ofplayground/react-pages, first as is and then withnodejs_compatand--mode staging, and ofplayground/tanstack-start. It checks that no migration runs and no local D1 database appears. Without this fix, it fails with both errors above.vp test runon the guide'svi.mock('void/kv')example and a page loader import. Because this changes the packed script, this PR runs the full packed suite.Verification
vp test run: 790 files pass, 1 skipped (16,479 tests pass, 30 skipped).vp run check: 33/33 pass.vp run build:docspasses.vp test runpasses in copies of the TanStack Start, React Router, vinext and SvelteKit playgrounds. In a copy ofplayground/react-pages, the guide'svi.mock('void/kv')example and the email testing example pass withvp test runand with plainvitest run, and no local D1 database appears.vp devstill serves/and/api/healthand applies the migration.vp test runpasses with and withoutnodejs_compat.void initstalled in its PTY while installing dependencies, before the new step.🤖 Generated with Claude Code