Testing
Test local changes with memory storage and a fake server response. You can test server handlers directly with a Request.
Create a client with memory storage
Use memory storage to avoid IndexedDB and browser storage in unit tests:
import { createMemoryStorageAdapter, preventRemoteWrites, valtioSync } from 'valtio-sync/client'
const sync = valtioSync({
endpoint: '/api/sync',
schema: { account, todos },
storage: createMemoryStorageAdapter({ namespace: 'test' }),
fetch: async (_input, init) => {
const request = JSON.parse(String(init?.body))
return Response.json({
serverSeq: 1,
accepted: request.ops.map((op) => ({
mutationId: op.mutationId,
collection: op.collection,
id: op.id,
serverVersion: 1,
})),
rejected: [],
changes: {},
})
},
})
await sync.hydrate()
The example assumes you have defined account and todos as shown in the Quickstart.
Inspect pending operations
Call flush() before inspecting pending operations:
sync.todos.create({ id: 'todo_1', title: 'Draft' })
await sync.flush()
expect(sync.debug.getPendingOps()).toMatchObject([
{ collection: 'todos', type: 'create', id: 'todo_1' },
])
Use fake timers
When testing the automatic write batch with fake timers, advance the batch window before inspecting the result:
vi.useFakeTimers()
sync.todos.records.todo_1.title = 'Changed'
await vi.advanceTimersByTimeAsync(100)
await sync.flush()
Test server handlers
Call the returned server's handle method with a Request:
import { valtioSync } from 'valtio-sync/server'
const syncServer = valtioSync({
schema: { account, todos },
handlers: {
todos: {
readSnapshot: () => ({
serverSeq: 1,
changes: {
upserted: [],
deleted: [],
},
}),
create: ({ record }) => ({ serverVersion: 1, record }),
},
},
})
const response = await syncServer.handle(
new Request('https://app.test/api/sync', {
method: 'POST',
body: JSON.stringify({
clientId: 'device_1',
schemaVersion: 1,
lastServerSeq: null,
ops: [
{
mutationId: 'm1',
collection: 'todos',
type: 'create',
id: 'todo_1',
value: { id: 'todo_1', title: 'Local' },
touched: ['id', 'title'],
},
],
}),
}),
)
Use debug.getLastSyncRequest() and debug.getLastSyncResponse() for end-to-end sync assertions.
Run UI scenarios with fixture data
Keep the real client and temporarily load an isolated memory adapter.
Install preventRemoteWrites first. This lets the client fetch remote changes while keeping fixture writes away from the endpoint. Restore the default adapter before removing protection:
const removeWriteProtection = sync.interceptTransport(preventRemoteWrites)
await sync.hydrate(createMemoryStorageAdapter({ namespace: `scenario:${scenarioId}` }))
try {
installScenarioState(sync)
} finally {
await sync.hydrate()
removeWriteProtection()
}
Argument-free hydrate() always reactivates the constructor-provided default adapter and reads its
current contents. Scenario cleanup must await it before removing write protection.