Test convention for all framework modules. Runner: bun test.
Location
The test lives next to the module it validates:
src/io/codec/csv.js
src/io/codec/csv.test.js
No separate __tests__/ or tests/ folder — strict co-location.
Minimum skeleton
import { describe, test, expect, beforeEach } from 'bun:test';
import { myModule } from './myModule.js';
describe('myModule module', () => {
test('should have correct module metadata', () => {
expect(myModule.name).toBe('myModule');
expect(myModule.dependencies).toEqual([]); // or ['dep1', 'dep2']
expect(typeof myModule.factory).toBe('function');
});
describe('factory', () => {
test('should create instance with expected API', () => {
const inst = myModule.factory();
expect(inst).toBeDefined();
expect(typeof inst.method1).toBe('function');
// ... all public members
});
});
describe('method1', () => {
let inst;
beforeEach(() => { inst = myModule.factory(); });
test('happy path', () => { /* ... */ });
test('edge case X', () => { /* ... */ });
test('throws on invalid input', () => {
expect(() => inst.method1(null)).toThrow();
});
});
});
Mandatory sections
Every *.test.js file must contain at minimum:
| Block | Role |
|---|---|
describe('<name> module', ...) |
Root container |
| Metadata test | Verifies name, dependencies, factory |
| Factory API test | Verifies that the factory returns all expected members |
describe(<method>) × N |
One per non-trivial public method |
| Error tests / edge cases | throw, empty values, invalid types |
If the module does round-trips (codec, serialisation), add:
| Block | Role |
|---|---|
describe('round-trip') |
decode(encode(x)) === x over several typical values |
Naming conventions
- The root
describeis'<name> module'(not'<Name>'nor'tests <name>'). - Sub-
describeblocks carry the exact name of the public method (e.g.'parseQuery', not'parsing queries'). testnames start with a verb or a fact, without a redundantshouldprefix. Acceptable:test('returns empty array on empty input', …)test('throws on unterminated quoted field', …)test('round-trip with custom delimiter', …)
Dependencies: manual factory wiring
Tests instantiate the factory directly without going through runtime.resolve — they control the dependencies:
import { mime } from './mime.js';
import { utf8 } from './utf8.js';
// Minimal stub for external deps outside the test's scope
const randomStub = {
bytes(n) { const out = new Uint8Array(n); crypto.getRandomValues(out); return out; }
};
const codec = mime.factory(utf8.factory(), randomStub);
No auto-resolution in tests. This:
- Guarantees isolation (no runtime cache effect).
- Explicitly documents dependencies.
- Allows stubbing where needed.
beforeEach rather than beforeAll
For modules with internal state (codec with buffer, queue with items in progress), use beforeEach to start from a fresh instance:
let inst;
beforeEach(() => { inst = myModule.factory(); });
beforeAll is reserved for immutable expensive setups (e.g. pre-computing a large reference vector).
Test vectors
When a standard provides official vectors (RFC, CAVP, Appendix), include them as-is:
describe('RFC 4648 §10 test vectors', () => {
const vectors = [
['', ''],
['f', 'Zg=='],
['fo', 'Zm8='],
// ...
];
for (const [input, expected] of vectors) {
test(`encode ${JSON.stringify(input)} → ${expected}`, () => {
expect(codec.encode(input)).toBe(expected);
});
}
});
What we do not test
- Private implementation details (internal variables,
_xxxhelpers). - Behaviour of the DI runtime (covered by
runtime.test.js). - Code from dependencies (covered by their own tests).
Running
# One file
bun test src/io/codec/csv.test.js
# A directory
bun test src/io/codec/
# The whole framework
bun test packages/front/fw/
Expected output:
552 pass
0 fail
1115 expect() calls
Ran 552 tests across 11 files. [111.00ms]
No test must fail or be skipped before a module is considered delivered.
Special cases
Worker-safe module tested outside a worker
Worker-safe modules can be tested directly in the bun main thread — workers are not instantiated in tests.
DOM module (not worker-safe)
Bun exposes a minimal DOM via happy-dom or jsdom. These modules often require an explicit setup — see the existing dom/events tests for the pattern.
Crypto / NIST CAVP vectors
Keep official vectors as commented hex strings with their reference (COUNT=0, archive, CSRC URL). Do not rewrite as binary literals.
Review checklist
Before considering a test suite complete:
- Module metadata tested
- All public members have at least one
describe - Error cases /
throwcovered - Round-trip if applicable
- Official vectors if a standard is implemented
- No
test.skip/test.onlyleft -
bun test <file>passes at 100%