Vitest
Config helpers and setup hooks for node-mode unit and e2e tests.
Use the vitest subpath to run node-mode unit and e2e tests against a devstack stack.
import { defineConfig } from 'vitest/config';
import {
devstackVitestServerConfig,
devstackVitestTestConfig,
} from '@mysten-incubation/devstack/vitest';
export default defineConfig({
server: devstackVitestServerConfig(),
test: devstackVitestTestConfig({
threads: 'single',
testSetup: { requireDevstack: true },
}),
});Vitest reads your vite.config.ts by default, so it inherits the devstackVitePlugin() you already
have there — that's what resolves the @generated alias when you import generated code under test.
If you don't have a vite.config.ts, add plugins: [devstackVitePlugin()] here too, or the alias
won't resolve.
The @mysten-incubation/devstack/vitest subpath exports:
devstackVitestTestConfig— build thetestblock fordefineConfig; pass{ autoBoot: true }to boot and tear down a stack around the run.devstackVitestServerConfig— build theserverblock fordefineConfig.useDevstackTestSetup— wire boot/teardown into a shared setup file with your ownbeforeAll/afterAll. This is the programmatic equivalent of thetestSetupconfig knob above: usetestSetupto letdevstackVitestTestConfigwire the bundled setup file for you, oruseDevstackTestSetupwhen you own a setup file and want to call the hooks yourself. Both take the same options (e.g.{ requireDevstack: true }).runDevstackBeforeAll/runDevstackAfterAll— the boot and teardown steps on their own, if you prefer to call them directly instead of throughuseDevstackTestSetup.getStackContext— read the booted stack's context (endpoints, manifest) inside a test.loadStackContext— load that context from disk outside the Vitest setup lifecycle.resolveVitestEnv— resolve the devstack env (stack name, deployment file) the same way the config helpers do.
Programmatic setup is useful when you own a shared setup file:
import { afterAll, beforeAll } from 'vitest';
import { useDevstackTestSetup } from '@mysten-incubation/devstack/vitest';
useDevstackTestSetup({ beforeAll, afterAll }, { requireDevstack: true });Inside a test, read the booted stack's context to find an endpoint:
import { expect, test } from 'vitest';
import { getStackContext } from '@mysten-incubation/devstack/vitest';
test('has a wallet endpoint', () => {
const ctx = getStackContext();
expect(ctx?.endpoint('wallet-app')).toMatch(/^http/);
});Unless you set autoBoot: true, these helpers don't start a stack for you. Run devstack up
yourself, or run devstack apply before tests that only need a deployment file and a manifest. If a
matching stack is already live, apply reuses it instead of starting a second one.
Running against an already-deployed stack
When a stack is already booted — your pnpm dev stack, a CI stack from an earlier step, or any
named stack — point the suite at it instead of booting a fresh one. The autoBoot config wires a
globalSetup that reuses the running stack when DEVSTACK_TEST_REUSE=1 is set: it skips the boot
and the teardown and only publishes the env handoff (DEVSTACK_STACK + DEVSTACK_MANIFEST_PATH,
and DEVSTACK_DEPLOYMENT_FILE for the generated config) to the existing manifest.
import {
devstackVitestServerConfig,
devstackVitestTestConfig,
} from '@mysten-incubation/devstack/vitest';
import { defineConfig } from 'vitest/config';
export default defineConfig({
server: devstackVitestServerConfig(),
test: devstackVitestTestConfig({ autoBoot: true, test: { testTimeout: 60_000 } }),
});Run it against a stack you brought up yourself — DEVSTACK_STACK selects which one, and
DEVSTACK_TEST_REUSE=1 switches the boot seam into reuse mode:
# In one terminal: bring the stack up (or it's already running from `pnpm dev`).
DEVSTACK_STACK=staging devstack up
# In another: run the e2e suite against it — no boot, no teardown.
DEVSTACK_STACK=staging DEVSTACK_TEST_REUSE=1 vitest run --config vitest.e2e.config.tsInside a test, read the live deployment the running stack published and run a transaction against
it. loadDeployment() resolves the deployment file the reuse step published; forNetwork returns
that network's rpc and resolved package ids:
import { SuiClient } from '@mysten/sui/client';
import { expect, test } from 'vitest';
import { loadDeployment } from '@generated/config-runtime.js';
import { createCounterTx, readCounter } from '../../src/counter.js';
test('runs a transaction against the already-running stack', async () => {
const deployment = loadDeployment();
const net = deployment.forNetwork(deployment.defaultNetwork);
const client = new SuiClient({ url: net.rpc });
// …create + read a counter against `net`'s published ids…
const id = await createCounterTx(client, net);
expect(await readCounter(client, id)).toBe(0n);
});Reuse mode never tears the stack down, so the same stack survives across runs — convenient for iterating, but it means your suite shares state with whatever else is using that stack.
How a test picks up live ids
The generated src/generated tree is id-free: every on-chain value resolves at runtime through
loadDeployment(). In the browser build that value comes from the build-injected
__DEVSTACK_DEPLOYMENT__ global (see Codegen). Under Vitest that global is
null, so config reads fall back to the deployment file the setup step booted:
- The setup step boots the stack and writes the deployment-file path to
process.env.DEVSTACK_DEPLOYMENT_FILE. - The first
loadDeployment()call reads that file when__DEVSTACK_DEPLOYMENT__isnull.
The result: config reads inside a test see the live ids of the stack the setup step booted, with no extra wiring.