{"record":{"id":"36ca60f8284e63c3","repo":"denoland/deno","slug":"after-requires-a-function-argument","errorCode":null,"errorMessage":"after() requires a function argument","messagePattern":"after\\(\\) requires a function argument","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/node/polyfills/testing.ts","lineNumber":2171,"sourceCode":"      ArrayPrototypePush(tapSuite.beforeAllHooks ??= [], fn);\n      return;\n    }\n    ArrayPrototypePush(rootBeforeHooks, fn);\n    // A bare top-level `before()` with no tests must still produce TAP\n    // output (`before` runs, then `TAP version 13`, then `1..0`).\n    scheduleTapRun();\n    return;\n  }\n  if (currentSuite) {\n    ArrayPrototypePush(currentSuite.beforeAllHooks, fn);\n    return;\n  }\n  ArrayPrototypePush(rootBeforeHooks, fn);\n}\n\nfunction after(fn, _options) {\n  if (typeof fn !== \"function\") {\n    throw new TypeError(\"after() requires a function argument\");\n  }\n  if (isTapMode()) {\n    const tapSuite = getTapCurrentSuite();\n    if (tapSuite !== null) {\n      ArrayPrototypePush(tapSuite.afterAllHooks ??= [], fn);\n      return;\n    }\n    ArrayPrototypePush(rootAfterHooks, fn);\n    scheduleTapRun();\n    return;\n  }\n  if (currentSuite) {\n    ArrayPrototypePush(currentSuite.afterAllHooks, fn);\n    return;\n  }\n  ArrayPrototypePush(rootAfterHooks, fn);\n}\n","sourceCodeStart":2153,"sourceCodeEnd":2189,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/node/polyfills/testing.ts#L2153-L2189","documentation":"The module-level after(fn) from node:test registers a one-time teardown hook (attached to the current TAP suite when in TAP mode, otherwise to the root hooks). Like before(), it validates its first argument is a function and throws TypeError 'after() requires a function argument' for anything else, including a called function's return value.","triggerScenarios":"after() with no argument; after(teardown()) passing a Promise; passing an options object in the fn position.","commonSituations":"Cleanup helpers invoked instead of referenced during refactors; optional teardowns that are undefined behind feature flags.","solutions":["Pass the function reference: after(closeDatabase)","Guard optional hooks: if (teardown) after(teardown)","Keep the (fn, options) argument order"],"exampleFix":"// before\nafter(closePool());\n\n// after\nafter(closePool);","handlingStrategy":"type-guard","validationCode":"import { after } from 'node:test';\n\nif (typeof teardown !== 'function') {\n  throw new TypeError(`after: expected function, got ${typeof teardown}`);\n}\nafter(teardown);","typeGuard":"const isHookFn = (fn) => typeof fn === 'function';\n\nif (isHookFn(teardown)) {\n  after(teardown);\n}","tryCatchPattern":null,"preventionTips":["Reference teardown functions; do not invoke them at registration","Keep the (fn, options) argument order","Guard optional teardowns with an if before registering"],"tags":["node-test","hooks","bdd","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"}