{"record":{"id":"dedfcdb70b6a59be","repo":"denoland/deno","slug":"after-requires-a-function","errorCode":null,"errorMessage":"after() requires a function","messagePattern":"after\\(\\) requires a function","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/node/polyfills/testing.ts","lineNumber":1613,"sourceCode":"      this.#subtestPromises.length,\n    );\n    for (const p of new SafeArrayIterator(promises)) {\n      try {\n        await p;\n      } catch { /* failures already reported through the subtest's own step */ }\n    }\n  }\n\n  before(fn, _options) {\n    if (typeof fn !== \"function\") {\n      throw new TypeError(\"before() requires a function\");\n    }\n    ArrayPrototypePush(this.#beforeHooks, fn);\n  }\n\n  after(fn, _options) {\n    if (typeof fn !== \"function\") {\n      throw new TypeError(\"after() requires a function\");\n    }\n    ArrayPrototypePush(this.#afterHooks, fn);\n  }\n\n  beforeEach(fn, _options) {\n    if (typeof fn !== \"function\") {\n      throw new TypeError(\"beforeEach() requires a function\");\n    }\n    ArrayPrototypePush(this.#beforeEachHooks, fn);\n  }\n\n  afterEach(fn, _options) {\n    if (typeof fn !== \"function\") {\n      throw new TypeError(\"afterEach() requires a function\");\n    }\n    ArrayPrototypePush(this.#afterEachHooks, fn);\n  }\n","sourceCodeStart":1595,"sourceCodeEnd":1631,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/node/polyfills/testing.ts#L1595-L1631","documentation":"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'.","triggerScenarios":"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.","commonSituations":"Invoking the cleanup instead of referencing it (a classic with arrow-less refactors); copy-paste from a hook that took a name string first.","solutions":["Pass the function reference: t.after(closeDb)","Wrap immediate calls: t.after(() => closeDb()) only if you truly need to call it later","Ensure dynamic hook registration checks typeof fn === 'function' first"],"exampleFix":"// before\nt.after(disposeHandles());\n\n// after\nt.after(disposeHandles);","handlingStrategy":"type-guard","validationCode":"if (typeof teardown !== 'function') {\n  throw new TypeError(`t.after: expected function, got ${typeof teardown}`);\n}\nt.after(teardown);","typeGuard":"const isHookFn = (fn) => typeof fn === 'function';\n\nif (isHookFn(teardown)) {\n  t.after(teardown);\n}","tryCatchPattern":null,"preventionTips":["Reference cleanup functions; do not call them at registration time","Wrap only when deferral is intended: t.after(() => close())","Guard optional teardowns: if (teardown) t.after(teardown)"],"tags":["node-test","hooks","api-misuse"],"backgroundTag":"test-hook-requires-function","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}