Test Runner IILoading saved progress…

Test Runner II

Build a tiny test runner — a stripped-down Jest — whose headline feature is nested suites. You register tests with test(name, fn) and group them with describe(name, fn), where a describe can contain other describes. When the suite runs, every test must be reported by its full spec path: the names of all the suites wrapped around it, joined to the test's own name. A bare 'adds' is useless when three suites each have an adds test; 'math > addition > adds' points straight at the failing one.

Signature

// createTestRunner() takes no arguments and returns four functions.
function createTestRunner(): {
  // Open a suite scope. Calls to describe/test inside fn nest under `name`.
  describe(name: string, fn: () => void): void;
  // Register a test. It PASSES if fn does not throw, FAILS if it throws.
  test(name: string, fn: () => void): void;
  it(name: string, fn: () => void): void; // alias for test
  // Execute every registered test, in registration order.
  run(): {
    // One row per test. `name` is the full path; `error` (the thrown
    // message) is present only when status is 'fail'.
    results: Array<{ name: string; status: 'pass' | 'fail'; error?: string }>;
    summary: { passed: number; failed: number; total: number };
  };
};

Examples

// One describe level: the test name is prefixed with its suite, joined by ' > '.
const { describe, test, run } = createTestRunner();
describe('math', () => {
  test('adds', () => {});
});
run();
// → {
//     results: [{ name: 'math > adds', status: 'pass' }],
//     summary: { passed: 1, failed: 0, total: 1 },
//   }
// Nesting and a failure. Sibling tests still run; the full path is reported.
const { describe, test, run } = createTestRunner();
describe('parser', () => {
  describe('numbers', () => {
    test('parses 42', () => {});
    test('rejects NaN', () => {
      throw new Error('got NaN');
    });
  });
});
run();
// → {
//     results: [
//       { name: 'parser > numbers > parses 42', status: 'pass' },
//       { name: 'parser > numbers > rejects NaN', status: 'fail', error: 'got NaN' },
//     ],
//     summary: { passed: 1, failed: 1, total: 2 },
//   }

Notes

  • The separator is ' > '. Join the enclosing describe names plus the test name with the string ' > ' (space, greater-than, space). A top-level test with no surrounding describe is reported as just its own name.
  • A test passes if its body does not throw. Run each test's fn; if it returns normally the test is 'pass'. If it throws, the test is 'fail' and you record the thrown error's message.
  • One failure must not stop the rest. A throwing test is recorded as a fail, but every other test still runs. Capture the error; never let it escape run().
  • Registration order is preserved. The results array lists tests in the order their test(...) calls ran, regardless of how deeply they were nested.
  • Synchronous only. Every fn runs to completion synchronously. You do not need to support async/await tests (that is the sibling test-runner-iv), nor beforeEach/afterEach hooks (that is test-runner-iii).
  • An empty describe is fine. A describe whose body registers no tests simply contributes nothing to the results.
Loading editor…