@venturekit/testing
v0.0.41
Published
Integration & end-to-end test harness for VentureKit apps — launch the local API stack, mint cognito-local users, reset the database, and drive a typed API client.
Maintainers
Readme
@venturekit/testing
Integration & end-to-end test harness for VentureKit apps. It owns the hard, framework-specific plumbing so your project only has to write the actual test scenarios:
- Launch a real local stack —
startTestStack()runsvk migratethenvk dev, and waits until the server is ready. - Gate on readiness —
waitForReady()polls the dev server's/_dev/healthprobe (the same liveness endpointvktooling uses). - Seed auth —
createTestUser()provisions a cognito-local user with a permanent password over thevk devadmin API (no AWS SDK needed);loginAs()additionally mints real JWTs for API-level tests. - Reset the database —
truncateAllTables()clears app tables between specs while preserving the VentureKit migration/seed bookkeeping. - Drive the API —
createApiClient()is a typedfetchwrapper that understands VentureKit's{ data }/{ error }envelope.
It's UI-framework agnostic: pair it with Playwright, Cypress, or plain vitest. The scenarios (selectors, flows, assertions) stay in your project.
Requires Docker (for the local Postgres / MinIO / cognito-local stack), so run these on macOS / Linux / CI — not over a Windows UNC share.
Install
pnpm add -D @venturekit/testing
# Optional peers (only if you use the matching helpers):
# pg -> ensureStageDatabase (creates the stage DB)
# @playwright/test -> the /playwright subpath
# @venturekit/data -> truncate* DB helpers
# @aws-sdk/client-cognito-identity-provider -> signInWithPassword / loginAsPlaywright: vkWebServers
The @venturekit/testing/playwright subpath builds the webServer block
for you. It exists because the ordering is unforgiving and every mistake
fails somewhere far from the cause:
- The database has to be created first. Neither
vk migratenorvk devcreates it, andglobalSetupis too late — Playwright startswebServerentries as config plugins, which run beforeglobalSetup. So creation is chained into the command, ahead of the migration. vk migrateandvk devmust agree on the database.vk devderives<database>_<stage>fromvk.config.tsand ignores an inheritedDATABASE_URL;vk migratehonours it. Pick your own name and you migrate one database while the API serves another empty one — the suite then fails withrelation "…" does not exist.- Crons must be off. They share the single-process dev server with the requests under test, write to the rows the specs assert on, and can spend real provider quota.
// playwright.config.ts
import { defineConfig } from '@playwright/test';
import { vkWebServers, storageStatePath } from '@venturekit/testing/playwright';
export default defineConfig({
testDir: './tests',
globalSetup: './setup/global-setup.ts',
projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'api',
dependencies: ['setup'],
use: { baseURL: 'http://127.0.0.1:4100', storageState: storageStatePath('admin') },
},
],
webServer: vkWebServers({
stage: 'test',
// `infrastructure.databases[].name` from vk.config.ts. The stage suffix
// is added for you, so this run uses `acme_app_test`.
database: 'acme_app',
api: { filter: '@acme/api', port: 4100 },
web: {
name: 'Admin',
command: 'pnpm --filter @acme/admin exec next dev -p 3100',
url: 'http://127.0.0.1:3100',
env: { NEXT_PUBLIC_API_BASE: 'http://127.0.0.1:4100' },
},
}),
});That single call creates acme_app_test, migrates and seeds it, boots
vk dev --no-watch --no-crons, gates on /_dev/health, names both servers
so a timeout says which one hung, and pipes their output.
Roles once, not per spec
establishRoles provisions each cognito-local user, logs it in, and writes
the cookie jar. Specs adopt a role by pointing at the file:
// setup/roles.setup.ts
import { test as setup } from '@playwright/test';
import { establishRoles } from '@venturekit/testing/playwright';
setup('establish roles', async () => {
await establishRoles({
baseUrl: 'http://127.0.0.1:4100',
roles: {
admin: { email: '[email protected]', password: 'Passw0rd!', attributes: { tenantId: 'dev' } },
viewer: { email: '[email protected]', password: 'Passw0rd!', attributes: { tenantId: 'dev' } },
},
});
});// setup/global-setup.ts
import { wipeAuthDir } from '@venturekit/testing/playwright';
// A jar from a previous run points at users the fresh database no longer
// has; the resulting 401s look like an auth bug.
export default async function globalSetup() {
await wipeAuthDir();
}For an authz matrix, createVkTest adds an asRole() fixture that opens
(and disposes) a context per role inside one spec:
// tests/api/fixtures.ts
import { createVkTest } from '@venturekit/testing/playwright';
export const test = createVkTest();
// tests/api/authz.spec.ts
import { expect } from '@playwright/test';
import { test } from './fixtures.js';
test('viewers cannot publish', async ({ asRole }) => {
const viewer = await asRole('viewer');
expect((await viewer.post('/content/1/publish')).status()).toBe(403);
});API-level integration tests (vitest)
No browser — boot the stack, mint a token, and hit the API directly:
import { afterAll, beforeAll, expect, test } from 'vitest';
import { startTestStack, loginAs, createApiClient, type TestStack } from '@venturekit/testing';
let stack: TestStack;
beforeAll(async () => {
stack = await startTestStack({ port: 4100, seed: true });
}, 180_000);
afterAll(async () => {
await stack?.stop();
});
test('GET /tenant returns the current tenant', async () => {
const { idToken } = await loginAs({
baseUrl: stack.baseUrl,
email: '[email protected]',
password: 'Passw0rd!',
});
const api = createApiClient({ baseUrl: stack.baseUrl, token: idToken });
const res = await api.get<{ slug: string }>('/tenant');
expect(res.status).toBe(200);
expect(res.data.slug).toBeDefined();
});API reference
| Export | Purpose |
| --- | --- |
| startTestStack(opts) | Migrate + boot vk dev; returns { baseUrl, port, stop() }. |
| waitForReady(opts) | Poll until ready (default /_dev/health). vkDevServerReady asserts the vk discriminator. |
| createApiClient(opts) | Typed fetch client; unwraps { data }, throws ApiError on non-2xx. |
| createTestUser(opts) | Idempotently create a cognito-local user with a permanent password. |
| setTestUserAttributes / deleteTestUser / getDevPools | Manage cognito-local users / inspect pools. |
| signInWithPassword(opts) / loginAs(opts) | Mint real JWTs for a user (needs the Cognito SDK peer). |
| truncateAllTables(opts) / truncateTables / listTables | Reset the DB between specs (needs @venturekit/data). |
| buildTruncateSql / filterTruncatableTables / quoteIdent | Pure SQL builders (reusable / testable). |
| ensureStageDatabase(opts) | Create <database>_<stage> if absent (needs pg). Idempotent and race-safe. |
| stageDatabaseName / stageDatabaseUrl / adminDatabaseUrl | Mirror the CLI's database derivation so migrate and vk dev agree. |
From @venturekit/testing/playwright (needs @playwright/test):
| Export | Purpose |
| --- | --- |
| vkWebServers(opts) | Build the webServer block: create DB → migrate → vk dev --no-crons. |
| establishRoles(opts) | Provision + log in every role, writing per-role storageState. |
| wipeAuthDir(dir?) | Clear stale storage state from globalSetup. |
| storageStatePath(role, dir?) | Where a role's jar lives (default .auth/<role>.json). |
| createVkTest(opts?) | test object with an asRole() fixture for authz specs. |
| ensureDbScriptPath() | Absolute path to the bundled creation script (also exposed as the vk-ensure-db bin). |
Notes
startTestStackdefaults to stagetest, which targets a separate local database (<dbname>_test) so your tests never clobbervk devdata. That name is derived by the CLI from<databases[].name>_<stage>and an inheritedDATABASE_URLdoes not override it — pointvk migrateand your own DB helpers at the same name, or the API will serve an unmigrated database.- Pass
--no-cronswhenever a test suite owns the stack. Scheduled tasks otherwise fire against the database under assertion, and a cron that calls an LLM provider spends real quota on every tick. Invoke them explicitly withPOST /_dev/invoke/cron/{name}when a spec needs one to run. vk devdoes not auto-migrate —startTestStackrunsvk migratefor you (passmigrate: falseto skip,seed: trueto also seed).- DB helpers read the same
DATABASE_URL/DB_*env the app uses.
