Testing
Sawdust's service-locator design makes tests deterministic: swap in a mock, assert the calls,
reset. This works in Jest and Vitest alike — the API is identical (jest.fn ↔ vi.fn).
Reset between tests
Both the logger and RUM locators expose reset helpers. Call them in a beforeEach / afterEach
pair so state never leaks across suites.
import { setLogger, resetLoggerLocator, noopLogger } from '@cues/sawdust'
import { setRumClient, resetRumClientLocator, createRumClient } from '@cues/sawdust-datadog/rum'
beforeEach(() => {
resetLoggerLocator()
resetRumClientLocator()
setLogger(noopLogger)
setRumClient(createRumClient())
})
afterEach(() => {
resetLoggerLocator()
resetRumClientLocator()
})
This guarantees the logger façade always delegates to a noop instance unless a test explicitly
installs another one.
Mock the logger
Stub the minimal BaseLogger contract or the full LoggerImplementation, depending on what
your code calls. For utility libraries the base contract is usually enough.
function createMockLogger() {
const calls: Array<{ level: string; args: unknown[] }> = []
const make = (level: string) => (...args: unknown[]) => calls.push({ level, args })
const logger = {
trace: make('trace'),
debug: make('debug'),
info: make('info'),
warn: make('warn'),
error: make('error'),
fatal: make('fatal'),
child: () => logger,
}
return { calls, logger }
}
test('logs during work', () => {
const mock = createMockLogger()
setLogger(mock.logger)
doWork()
expect(mock.calls.some((c) => c.args[0] === 'work done')).toBe(true)
})
Need the full fluent API? Start from noopLogger and spy on it, or inject a lightweight
implementation that satisfies LoggerImplementation.
Mock RUM
The RUM locator behaves the same way. Inject a noop client or stub the methods you expect to fire (see the full stub in the RUM guide).
import { setRumClient, resetRumClientLocator } from '@cues/sawdust-datadog/rum'
beforeEach(() => {
resetRumClientLocator()
setRumClient(createMockRum())
})
Test utilities
- Spying on calls — because the façade always points at the current singleton, spying on
getLogger()/getRumClient()gives you the live instance. - Temporary overrides — call
setLogger(other)/setRumClient(other)mid-test; alwaysreset…afterward to avoid leaks. - Async context — when testing request-scoped logging, use
withRequestContextwith a mocked logger and assert the child calls (see Request Scope).
Keep resets symmetrical and the service locator stays predictable across suites.