{"record":{"id":"2aab9f86bc06da54","repo":"denoland/deno","slug":"aftereach-requires-a-function","errorCode":null,"errorMessage":"afterEach() requires a function","messagePattern":"afterEach\\(\\) requires a function","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/node/polyfills/testing.ts","lineNumber":1627,"sourceCode":"  }\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\n  // Runs this context's `before()` hooks a single time, before its first\n  // subtest. Idempotent so it is safe to call from every subtest's step.\n  async _runBeforeHooksOnce() {\n    if (this.#beforeHooksRun) return;\n    this.#beforeHooksRun = true;\n    // deno-lint-ignore no-this-alias\n    const ctx = this;\n    for (const hook of new SafeArrayIterator(this.#beforeHooks)) {\n      await runInTestContext(ctx, () => hook(ctx));\n    }\n  }\n\n  // Runs this context's `after()` hooks a single time, after its body and all\n  // of its subtests have completed. Idempotent so success and failure paths can","sourceCodeStart":1609,"sourceCodeEnd":1645,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/node/polyfills/testing.ts#L1609-L1645","documentation":"t.afterEach(fn) on the node:test context registers a hook run after each of this test's subtests. As with the sibling hooks, only a function argument is accepted; anything else throws TypeError 'afterEach() requires a function'.","triggerScenarios":"t.afterEach() with no argument; t.afterEach(truncateTable()) passing a Promise; passing a config object in the fn position.","commonSituations":"Cleanup helpers called instead of referenced; optional cleanup that is undefined when a feature flag is off.","solutions":["Pass the function reference: t.afterEach(truncateTable)","Guard optional hooks: if (cleanup) t.afterEach(cleanup)","Keep the (fn, options) argument order"],"exampleFix":"// before\nt.afterEach(cleanUp());\n\n// after\nt.afterEach(cleanUp);","handlingStrategy":"type-guard","validationCode":"if (typeof cleanup !== 'function') {\n  throw new TypeError(`t.afterEach: expected function, got ${typeof cleanup}`);\n}\nt.afterEach(cleanup);","typeGuard":"const isHookFn = (fn) => typeof fn === 'function';\n\nif (isHookFn(cleanup)) {\n  t.afterEach(cleanup);\n}","tryCatchPattern":null,"preventionTips":["Pass the cleanup function itself, not its return value","Keep the (fn, options) argument order","Skip registration when an optional cleanup is absent rather than passing undefined"],"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"}