Why mocha-chain
The Problem
Mocha's beforeEach hook is the standard way to set up fresh state before each test. But using it with TypeScript reveals a tension between type safety and test isolation.
beforeEach forces unsafe variable declarations
The natural pattern is to declare a variable in the suite scope, assign it in beforeEach, and use it in tests:
suite('Division.divide', () => {
let division: Division | null = null;
beforeEach(() => {
division = new Division();
});
test('success', () => {
expect(division!.divide(12, 3)).to.equal(4);
});
});TypeScript knows division could be null — it was declared that way. So every test has to assert non-null with !, or cast the type, or initialize with a fake value. The type annotation lies: the variable is always set by test time, but nothing in the type system reflects that guarantee.
Hoisting the variable out doesn't help for mutable state
The obvious fix is to construct once and share:
const division = new Division();Now the type is clean. But this only works for immutable values. The moment the object has state — a database connection, a Sinon mock, a per-test counter — sharing it across tests means one test can corrupt another.
Per-test construction without hooks violates DRY
You could construct inside each test body:
test('success', () => {
const division = new Division();
expect(division.divide(12, 3)).to.equal(4);
});But this loses everything that makes hooks useful: setup runs once per context, teardown is guaranteed even on failure, and shared setup logic lives in one place.
The Solution
mocha-chain lets hooks return values that are passed forward as typed context. Instead of a shared mutable variable in the outer scope, each hook returns an object and the next hook or test receives it as a parameter:
const withDivision = beforeEach(() => {
return { division: new Division() };
});
withDivision.test('success', ({ division }) => {
expect(division.divide(12, 3)).to.equal(4);
});divisionis alwaysDivision, neverDivision | null- No
!assertions, no unsafe casts - Each test gets a fresh instance from the hook, isolated from every other test
- Teardown hooks (
afterEach,after) receive the same context, so cleanup is type-safe too
vitest-chain solves the same problem for Vitest.