Mysten Incubation
Testing

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.

vitest.config.ts
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 the test block for defineConfig; pass { autoBoot: true } to boot and tear down a stack around the run.
  • devstackVitestServerConfig — build the server block for defineConfig.
  • useDevstackTestSetup — wire boot/teardown into a shared setup file with your own beforeAll/afterAll. This is the programmatic equivalent of the testSetup config knob above: use testSetup to let devstackVitestTestConfig wire the bundled setup file for you, or useDevstackTestSetup when 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 through useDevstackTestSetup.
  • 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:

test/setup.ts
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.

vitest.e2e.config.ts
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.ts

Inside 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:

tests/e2e/reuse.test.ts
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:

  1. The setup step boots the stack and writes the deployment-file path to process.env.DEVSTACK_DEPLOYMENT_FILE.
  2. The first loadDeployment() call reads that file when __DEVSTACK_DEPLOYMENT__ is null.

The result: config reads inside a test see the live ids of the stack the setup step booted, with no extra wiring.

On this page