lowkey-test
v0.1.0
Published
A tiny TypeScript test library and runner with:
Readme
lowkey-test
A tiny TypeScript test library and runner with:
- Jest/Chai-style assertions
- Mocks and spies
- Lifecycle hooks (
beforeAll,afterEach, etc.) - Built-in reporters
- Fake timers
- AsyncLocalStorage leak guard helpers
- GC assertions via
WeakRef+FinalizationRegistry
Install
pnpm add -D lowkey-testFor this repository itself:
pnpm install
pnpm build
pnpm testQuick Start
import { describe, test, expect, run } from 'lowkey-test';
describe('math', () => {
test('adds', () => {
expect(1 + 1).toBe(2);
});
});
const result = await run();
if (result.failed > 0) process.exit(1);Core API
Suites and tests
import {
describe,
test,
it,
beforeAll,
afterAll,
beforeEach,
afterEach,
} from 'lowkey-test';
describe('group', () => {
beforeAll(() => {});
afterAll(() => {});
beforeEach(() => {});
afterEach(() => {});
test('works', () => {});
it('alias of test', () => {});
test.skip('skip this');
test.todo('implement later');
test.only('run only this test', () => {});
});
describe.only('run only this suite', () => {});
describe.skip('skip this suite', () => {
test('not executed', () => {});
});Per-test timeout:
test(
'slow case',
async () => {
// ...
},
{ timeout: 10_000 },
);Runner
import { configure, run, reset } from 'lowkey-test';
configure({
timeout: 5000,
parallel: true,
concurrency: 4,
context: false,
});
const result = await run();
// result: { suites, tests, passed, failed, skipped, duration }
reset(); // clear registered suites/tests (useful between file batches)Assertions (expect)
Common matchers
toBe,toEqual,toStrictEqualtoBeTruthy,toBeFalsy,toBeNull,toBeUndefined,toBeDefined,toBeNaNtoBeGreaterThan,toBeGreaterThanOrEqual,toBeLessThan,toBeLessThanOrEqual,toBeCloseTotoContain,toContainEqual,toHaveLength,toHavePropertytoMatch,toMatchObjecttoThrow,toThrowErrortoBeInstanceOf,toBeTypeOf
Negation:
expect(value).not.toBe(other);Chai-like aliases:
expect(42).equal(42);
expect({ a: 1 }).deep.equal({ a: 1 });Async chains:
await expect(Promise.resolve(5)).resolves.toBe(5);
await expect(Promise.reject(new Error('x'))).rejects.toThrow('x');Mocks and Spies
import { createMock, spyOn, expect } from 'lowkey-test';
const fn = createMock<(x: number) => number>()
.mockImplementation((x) => x * 2)
.mockImplementationOnce(() => 99);
expect(fn(1)).toBe(99);
expect(fn(3)).toBe(6);
fn.toHaveBeenCalled();
fn.toHaveBeenCalledTimes(2);
fn.toHaveBeenCalledWith(1);
fn.toHaveBeenLastCalledWith(3);
const obj = { greet: (name: string) => `hi ${name}` };
const spy = spyOn(obj, 'greet').mockReturnValue('mocked');
expect(obj.greet('sam')).toBe('mocked');
spy.mockRestore();Also available:
mockReturnValue,mockReturnValueOncemockResolvedValue,mockResolvedValueOncemockRejectedValue,mockRejectedValueOncemockClear,mockReset,mockRestore- module registry helpers:
mockModule,unmockModule
Reporters
Available reporters:
DefaultReporterVerboseReporterDotReporterTapReporterJsonReporter
Programmatic usage:
import { configure, run, DotReporter } from 'lowkey-test';
configure({ reporters: [new DotReporter()] });
await run();JsonReporter writes results to lowkey-test-results.json by default.
Fake Timers
import {
useFakeTimers,
useRealTimers,
tick,
tickAsync,
runAllTimers,
runAllTimersAsync,
runOnlyPendingTimers,
advanceTo,
getTime,
clearAllTimers,
pendingTimerCount,
isFakeTimersInstalled,
} from 'lowkey-test';
useFakeTimers({ now: 0 });
setTimeout(() => {}, 100);
tick(100);
useRealTimers();Notes:
- Fakes include
setTimeout,setInterval, optionalsetImmediate, andDate. tickAsyncandrunAllTimersAsyncdrain microtasks between timer steps.realTimersexposes original timer/date APIs while fakes are installed.
AsyncLocalStorage Guard
Helpers for tracking context bleed/leaks:
trackALS,untrackALSinstallALSGuard,uninstallALSGuardassertNoALSLeak,assertNoBleed,detectBleedsnapshotALS,withALSGuardALSLeakError
Example:
import { AsyncLocalStorage } from 'async_hooks';
import {
trackALS,
installALSGuard,
assertNoALSLeak,
uninstallALSGuard,
} from 'lowkey-test';
const als = trackALS(new AsyncLocalStorage<{ requestId: string }>(), 'req');
installALSGuard();
als.run({ requestId: '123' }, () => {
// test body
});
assertNoALSLeak();
uninstallALSGuard();Garbage Collection Utilities
import { watchGC, gc, expect } from 'lowkey-test';
let obj: object | null = { data: 1 };
const handle = watchGC(obj);
obj = null; // drop strong ref
await gc();
await expect(handle).toBeGarbageCollected({ timeout: 3000 });Exports:
watchGC(obj) -> GCHandlegc(timeout?, interval?)- matcher:
toBeGarbageCollected
Test Context (AsyncLocalStorage)
Enable context mode so each test runs with an ALS store:
import { configure, test, useTestContext } from 'lowkey-test';
configure({ context: true });
test('ctx', () => {
const ctx = useTestContext();
// ctx: { name, suite, meta }
});CLI
src/cli.ts provides a file-discovery runner.
node dist/cli.js [files-or-dirs] [options]Options:
--parallelrun tests in parallel--concurrency <n>parallel worker chunk size (default4)--timeout <ms>default per-test timeout (default5000)--reporter <name>default|verbose|dot|tap|json--contextenable AsyncLocalStorage test context--watchrerun on file changes
Test file pattern matching is based on extensions like:
*.test.ts,*.spec.ts*.test.js,*.spec.js- also
mts,tsx,mjs,jsx
Type Exports
lowkey-test exports the primary runtime APIs and these types:
TestFn,HookFnTestResult,SuiteResult,RunResultReporter,LowkeyTestConfig,TestContextMatcherChainMockFn,MockCallFakeTimerOptionsALSLeakEntry,ALSGuardSnapshotGCHandle
