| name | manten |
| description | Manten testing library patterns - standalone imports via AsyncLocalStorage, async tests, describe groups, hooks, snapshots, timeouts, retries, and concurrency. Use when writing tests with manten, running node test files, or setting up a no-config test suite. |
Quick Reference
| API | Purpose |
|---|
test(name, fn, opts?) | Run a single test. Returns Promise<void> |
describe(name, fn, opts?) | Group tests. Returns Promise<void> |
expect(value) | Jest assertions |
expectSnapshot(value, name?) | Snapshot testing |
onTestFail(callback) | Debug hook (inside test()) |
onTestFinish(callback) | Cleanup hook (inside test()) |
onFinish(callback) | Cleanup hook (inside describe()) |
skip(reason?) | Skip current test or describe group |
setProcessTimeout(ms) | Global process timeout |
configure({ snapshotPath? }) | Snapshot configuration |
All APIs are standalone imports:
import {
test, describe, expect, skip, onTestFail, onTestFinish, onFinish, expectSnapshot
} from 'manten'
Each function automatically knows which test or group it belongs to via AsyncLocalStorage.
Core Pattern: Await Controls Flow
import { test, expect } from 'manten'
test('A', async () => { })
test('B', async () => { })
await test('first', async () => { })
await test('second', async () => { })
Node.js won't exit while promises are settling, so you don't need await to keep the process alive — only to enforce ordering.
Describe Blocks
import { describe, test } from 'manten'
describe('Auth', () => {
test('login', async () => { })
test('logout', async () => { })
})
Nesting:
describe('Outer', () => {
test('A', () => { })
describe('Inner', () => {
test('B', () => { })
})
})
Awaiting a group waits for all children:
await describe('Group', () => {
test('A', async () => { })
test('B', async () => { })
})
Timeouts & Retries
test() and describe() callbacks always receive { signal } — an AbortSignal that aborts on timeout or when the parent group is aborted:
test('fast test', async ({ signal }) => {
await fetch('/api', { signal })
}, 1000)
test('flaky test', async () => {
await unreliableAPI()
}, {
timeout: 5000,
retry: 3
})
Hooks
import { test, onTestFail, onTestFinish } from 'manten'
test('with cleanup', async () => {
const resource = await createResource()
onTestFinish(() => resource.cleanup())
onTestFail(error => console.log('Debug:', error))
})
onFinish runs after all tests in a describe():
import { describe, test, onFinish } from 'manten'
describe('Database', async () => {
const database = await connect()
onFinish(() => database.close())
test('query', () => { })
})
Hook errors are logged but don't fail the test. onFinish errors set process.exitCode = 1.
Skipping
import { test, skip } from 'manten'
test('linux only', () => {
if (process.platform !== 'linux') {
skip('Only runs on Linux')
}
})
Skip entire groups — skip() must be called before any test() or nested describe():
describe('GPU tests', () => {
if (!hasGPU) {
skip('GPU not available')
}
test('render', () => { })
})
Concurrency Control
describe('Database tests', () => {
test('query 1', async () => { })
test('query 2', async () => { })
test('query 3', async () => { })
}, { parallel: 2 })
Options: false (sequential), true (unbounded), number (limit), 'auto' (adapts to CPU load).
Tests that you explicitly await bypass the parallel queue.
Group Timeouts
describe('Suite', () => {
test('A', async () => { })
test('B', async () => { })
}, { timeout: 10_000 })
Individual test timeouts still apply — whichever is stricter wins.
Snapshot Testing
import { test, expectSnapshot } from 'manten'
test('user data', () => {
expectSnapshot(getUser(), 'initial user')
expectSnapshot(getUser())
})
Snapshots are stored in .manten.snap by default. Serialized with util.inspect — output may differ across Node.js versions, so regenerate after upgrading Node.
Update snapshots: MANTEN_UPDATE_SNAPSHOTS=1 node tests/index.ts
Configure path via configure() or MANTEN_SNAPSHOT_PATH env var:
import { configure } from 'manten'
configure({ snapshotPath: 'custom.snap' })
Splitting Tests Across Files
Import files inside describe() — they automatically nest under the parent group:
import { describe } from 'manten'
describe('my-app', async () => {
import('./specs/auth.ts')
import('./specs/api.ts')
})
import { describe, test, expect } from 'manten'
describe('Authentication', () => {
test('login', () => { })
test('logout', () => { })
})
Each file works standalone too — node tests/specs/auth.ts runs just that file.
Parameterized Test Files
Export a function wrapping describe() to pass data into test files:
import { describe, test, expect } from 'manten'
export const builds = (nodePath: string) => describe('builds', () => {
test('compiles', async () => {
const result = await run(nodePath)
expect(result.exitCode).toBe(0)
})
})
Since describe() doesn't run until called, these can be statically imported:
import { builds } from './specs/builds.ts'
import { describe } from 'manten'
describe('my-app', async () => {
for (const nodeVersion of ['v20', 'v22', 'v24']) {
const node = await getNode(nodeVersion)
await describe(`Node ${node.version}`, () => {
builds(node.path)
})
}
})
Export names should match the file name or describe() description.
Recommended Project Structure
tests/
specs/ # test files
utils/ # shared test helpers
fixtures/ # static test data
index.ts # entry point — run this
Running Tests
node tests/index.ts
node --watch tests/index.ts
TESTONLY='login' node tests/index.ts
MANTEN_UPDATE_SNAPSHOTS=1 node tests/index.ts
Process-Level Timeout
import { setProcessTimeout } from 'manten'
setProcessTimeout(10 * 60 * 1000)
Key Behaviors
- Tests run immediately — no collection phase.
test() executes as soon as it's called.
- No beforeAll/beforeEach — inline setup in each test, or use
describe() + onFinish() for shared resources.
- Exit code — failures set
process.exitCode = 1 but don't force-exit. All tests run to completion.
TESTONLY — matches against full title including describe prefixes (e.g. Auth › login). Case-sensitive.
signal — always provided to test() and describe() callbacks, not just when timeouts are set.
skip() in describe — must be called before any test() or nested describe().
- TypeScript — all APIs fully typed.
Test and Describe types exported for advanced use cases.
Migrating from v1
For migrating from manten v1 (callback destructuring) to v2 (standalone imports), see MIGRATION.md.
Related
For disposable file system fixtures, use fs-fixture with await using for automatic cleanup.