Testing
Use createTestEnv() from svelte-realtime/test to test your live functions without a real WebSocket server.
import { describe, it, expect, afterEach } from 'vitest';
import { createTestEnv } from 'svelte-realtime/test';
import * as chat from '../src/live/chat.js';
describe('chat module', () => {
const env = createTestEnv();
afterEach(() => env.cleanup());
it('sends and receives messages', async () => {
env.register('chat', chat);
const alice = env.connect({ id: 'alice', name: 'Alice' });
const bob = env.connect({ id: 'bob', name: 'Bob' });
// Subscribe Bob to the messages stream
const stream = bob.subscribe('chat/messages');
await new Promise(r => setTimeout(r, 10));
// Alice sends a message
const msg = await alice.call('chat/sendMessage', 'Hello!');
expect(msg.text).toBe('Hello!');
// Bob receives the live update
await new Promise(r => setTimeout(r, 10));
expect(stream.events).toHaveLength(1);
});
}); TestEnv API
| Method | Description |
|---|---|
register(moduleName, exports) | Register a module’s live functions |
connect(userData) | Create a fake connected client |
cleanup() | Clear all state (call in afterEach) |
platform | The mock platform object |
TestClient API
| Method | Description |
|---|---|
call(path, ...args) | Call a live() function |
subscribe(path, ...args) | Subscribe to a live.stream() |
binary(path, buffer, ...args) | Call a live.binary() function |
disconnect() / reconnect() | Simulate connection state changes |
TestStream API
| Property | Description |
|---|---|
value | Latest value from the stream |
error | Error if the stream failed |
topic | The topic the stream is subscribed to |
events | All pub/sub events received |
hasMore | Whether more pages are available |
waitFor(predicate, timeout?) | Wait for a value matching a predicate |
Integration testing your hooks.ws with a real WebSocket server
For tests that exercise the upgrade handshake, subscribe protocol, and pub/sub end-to-end, use createTestServer() from svelte-adapter-uws/testing. It boots a real uWebSockets.js server on a random port (typically in ~2ms) and exposes the full Platform API.
import { createTestServer } from 'svelte-adapter-uws/testing';
import { WebSocket } from 'ws';
import { describe, it, expect, afterEach } from 'vitest';
import * as myHandler from '../src/hooks.ws.js';
let server;
afterEach(() => server?.close());
it('rejects unauthenticated upgrades', async () => {
server = await createTestServer({ handler: myHandler });
const ws = new WebSocket(server.wsUrl);
const code = await new Promise((resolve) => {
ws.on('unexpected-response', (_, res) => resolve(res.statusCode));
ws.on('open', () => resolve('open'));
});
expect(code).toBe(401);
});
it('publishes to subscribers', async () => {
server = await createTestServer({ handler: myHandler });
const ws = new WebSocket(server.wsUrl, {
headers: { cookie: 'session=valid-token' }
});
await new Promise(r => ws.on('open', r));
ws.send(JSON.stringify({ type: 'subscribe', topic: 'todos' }));
await new Promise(r => setTimeout(r, 10));
const msg = new Promise(r => ws.on('message', d => r(JSON.parse(d.toString()))));
server.platform.publish('todos', 'created', { id: 1 });
expect(await msg).toMatchObject({ topic: 'todos', event: 'created' });
ws.close();
}); The test server uses the same subscribe / unsubscribe protocol as production and exposes publish, send, sendTo, topic, connections, subscribers, and assertions via server.platform. Use createTestEnv() (above) for fast in-memory unit tests of live() modules; reach for createTestServer() when you need to exercise hooks.ws, the upgrade path, or the wire protocol.
createTestServer options
server = await createTestServer({
handler: myHandler,
// Mirror of the production wsOptions; pass either to test the same
// behaviour your production app gets.
upgradeAdmission: { maxConcurrent: 100, perTickBudget: 16 },
// Other production-equivalents available:
// wsOptions: { maxBackpressure, idleTimeout, maxPayloadLength, ... },
// origin: '*' | 'same-origin' | string[],
// env: { ... } // ENV_PREFIX-aware env shim for SvelteKit `platform.env`
}); upgradeAdmission is the same { maxConcurrent, perTickBudget } shape the production handler accepts via adapter({ websocket: { upgradeAdmission } }). Passing it to createTestServer lets you assert admission shedding (503 on the upgrade path) end-to-end without booting a full SvelteKit app.
Curated re-exports from svelte-adapter-uws/testing
Downstream test code (extensions, app-side integration tests, custom transport bridges) often needs to assert on the same wire shapes the production runtime produces. The testing entry point re-exports a curated set of pure helpers and userData slot constants so you don’t redeclare helpers that would drift over time:
import {
createTestServer,
// wire-protocol helpers
esc, completeEnvelope, wrapBatchEnvelope,
isValidWireTopic, createScopedTopic,
// behaviour helpers
collapseByCoalesceKey, resolveRequestId, createChaosState,
// per-connection userData slot constants (use as Symbol keys on userData)
WS_SUBSCRIPTIONS, WS_COALESCED, WS_SESSION_ID,
WS_PENDING_REQUESTS, WS_STATS, WS_PLATFORM,
WS_CAPS, WS_REQUEST_ID_KEY
} from 'svelte-adapter-uws/testing'; Production-internal plumbing (mime lookup, byte parsing, cookie split, write-chunk backpressure, sampler internals, upgrade admission factory, origin allowlist matcher) is deliberately NOT re-exported so the test surface stays stable while production hot paths remain free to refactor.
Wrap-your-own-transport
createTestServer ships a working uWS instance for end-to-end tests. For tests that wrap a different transport (e.g. an in-process bridge for a Tauri / Capacitor harness, a Node ws-backed alternative, a stub for a third-party WebSocket emulator), the curated re-exports above are the supported contract: import the same wire helpers (completeEnvelope, isValidWireTopic) and userData slot constants the production code uses, build envelopes the same way, and your transport’s behaviour is asserted against the same shapes the production runtime produces. The internal byte-level handlers are not re-exported - if you need to fork those, copy them into your transport and pin the fork against a specific adapter version.
Schema-version replays - subscribeAt
subscribeAt(stream, { schemaVersion }) from svelte-realtime/test-client constructs a parallel store at a chosen client-side schema version and walks the real wire path. Useful for schema-migration demos, e2e tests that need to verify older client versions still work, and verifying the migrate callback on a versioned stream.
import { subscribeAt } from 'svelte-realtime/test-client';
import { messages } from '../src/live/chat.js';
it('legacy v1 clients see migrated payloads', async () => {
const v1 = subscribeAt(messages, { schemaVersion: 1 });
await v1.ready();
expect(v1.value[0]).toMatchObject({ msg: 'hello' }); // v1 shape
}); See Schema Evolution for the migration callback and version semantics.
Wire-protocol fuzz harness - npm run test:fuzz
The realtime package ships a CI-friendly wire-protocol fuzzer at test/fuzz-wire.mjs (~150 ms per pass). It feeds malformed envelopes, control bytes, oversized frames, and randomized topic shapes through the message handler and asserts the framework never throws or produces a corrupt internal state.
# Run against the bundled adapter
cd node_modules/svelte-realtime && npm run test:fuzz
# Run against a local adapter build
ADAPTER_ROOT=/path/to/svelte-adapter-uws npm run test:fuzz Most apps will not run the fuzzer locally; it lives in CI alongside the unit tests. If you fork or extend the wire protocol, add cases to fuzz-wire.mjs and run it before opening a PR.
Chaos harness - npm run test:chaos
Cross-instance pubsub stress test: two svelte-realtime instances behind a Redis container connected via docker-compose, with a configurable adversarial message generator. Validates that distributed pubsub, replay, and presence behave under packet loss, instance restarts, and Redis unavailability. See the realtime repo’s chaos/ directory for the harness shape.
Was this page helpful?