denoland/deno · error · TypeError

after() requires a function

Error message

after() requires a function

What it means

t.after(fn) on the node:test context registers a teardown hook that runs once after this test's subtests complete. Like the other context hooks it only stores functions; a non-function argument throws TypeError 'after() requires a function'.

Source

Thrown at ext/node/polyfills/testing.ts:1613

      this.#subtestPromises.length,
    );
    for (const p of new SafeArrayIterator(promises)) {
      try {
        await p;
      } catch { /* failures already reported through the subtest's own step */ }
    }
  }

  before(fn, _options) {
    if (typeof fn !== "function") {
      throw new TypeError("before() requires a function");
    }
    ArrayPrototypePush(this.#beforeHooks, fn);
  }

  after(fn, _options) {
    if (typeof fn !== "function") {
      throw new TypeError("after() requires a function");
    }
    ArrayPrototypePush(this.#afterHooks, fn);
  }

  beforeEach(fn, _options) {
    if (typeof fn !== "function") {
      throw new TypeError("beforeEach() requires a function");
    }
    ArrayPrototypePush(this.#beforeEachHooks, fn);
  }

  afterEach(fn, _options) {
    if (typeof fn !== "function") {
      throw new TypeError("afterEach() requires a function");
    }
    ArrayPrototypePush(this.#afterEachHooks, fn);
  }

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Pass the function reference: t.after(closeDb)
  2. Wrap immediate calls: t.after(() => closeDb()) only if you truly need to call it later
  3. Ensure dynamic hook registration checks typeof fn === 'function' first

Example fix

// before
t.after(disposeHandles());

// after
t.after(disposeHandles);
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof teardown !== 'function') {
  throw new TypeError(`t.after: expected function, got ${typeof teardown}`);
}
t.after(teardown);

Type guard

const isHookFn = (fn) => typeof fn === 'function';

if (isHookFn(teardown)) {
  t.after(teardown);
}

Prevention

When it happens

Trigger: t.after() with no argument; t.after(cleanupDb()) passing the Promise returned by an immediate call instead of the function; passing an options object or string.

Common situations: Invoking the cleanup instead of referencing it (a classic with arrow-less refactors); copy-paste from a hook that took a name string first.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/dedfcdb70b6a59be. Report an issue: GitHub.