{"record":{"id":"82231297ba643658","repo":"denoland/deno","slug":"plan-expected-this-expected-assertion-s-but-r","errorCode":null,"errorMessage":"plan expected ${this.#expected} assertion(s) but received ${this.#actual}","messagePattern":"plan expected (.+?) assertion\\(s\\) but received (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ext/node/polyfills/testing.ts","lineNumber":1353,"sourceCode":"  }\n  return undefined;\n}\n\nclass TestPlan {\n  #expected;\n  #actual = 0;\n\n  constructor(count) {\n    this.#expected = count;\n  }\n\n  increment() {\n    this.#actual++;\n  }\n\n  check() {\n    if (this.#actual !== this.#expected) {\n      throw new Error(\n        `plan expected ${this.#expected} assertion(s) but received ${this.#actual}`,\n      );\n    }\n  }\n}\n\nclass NodeTestContext {\n  #denoContext;\n  #afterHooks = [];\n  #beforeHooks = [];\n  #parent;\n  #skipped = false;\n  #name;\n  #abortController = new AbortController();\n  #plan;\n  #planAssert;\n  #beforeEachHooks = [];\n  #afterEachHooks = [];","sourceCodeStart":1335,"sourceCodeEnd":1371,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/node/polyfills/testing.ts#L1335-L1371","documentation":"t.plan(count) in node:test registers a TestPlan with the expected number of assertions. The test context wraps its assert object so every t.assert.* call (and t.assert.fileSnapshot) increments a counter, and when the body finishes _checkPlan() compares actual against expected. Any mismatch — too few or too many — throws 'plan expected N assertion(s) but received M'.","triggerScenarios":"t.plan(3) with only two assertions executed because an early return or if-branch skipped one; asserting more times than planned inside a loop; using the imported node:assert functions instead of t.assert.* so calls are never counted.","commonSituations":"Conditional assertions (if (debug) t.assert.ok(...)); loop-driven asserts whose iteration count varies with data; refactors that swap t.assert for the global assert module; forgetting that plan counts only this test's own asserts, not subtests.","solutions":["Make every planned assertion unconditional — move variability into assertion arguments, not around the assert calls","Recount after editing: add/remove assertions and update t.plan(n) in the same change","Verify you are calling t.assert.* (the context's wrapped object), not the imported node:assert — only the former is counted","Derive the count from data when looping: t.plan(cases.length) then one assert per case","If the exact count is unknowable, drop t.plan entirely — it is optional"],"exampleFix":"// before\ntest('sum', (t) => {\n  t.plan(3);\n  t.assert.equal(1 + 1, 2);\n  if (process.env.EXTRA) t.assert.ok(true); // skipped -> received 2\n});\n\n// after\ntest('sum', (t) => {\n  t.plan(2);\n  t.assert.equal(1 + 1, 2);\n  t.assert.ok(true);\n});","handlingStrategy":"validation","validationCode":"// dev-time dry run: count t.assert usage to derive the plan\nfunction planFromBody(t, body) {\n  let count = 0;\n  const assert = new Proxy(t.assert, {\n    get(target, prop) {\n      const v = Reflect.get(target, prop);\n      return typeof v === 'function'\n        ? (...args) => { count++; return v(...args); }\n        : v;\n    },\n  });\n  return { run: body({ ...t, assert }), count: () => count };\n}\n// use once locally, then hard-code t.plan(count) in the test","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only assertions through t.assert.* are counted — never mix in the imported node:assert module inside a planned test","Keep assertions unconditional; move variability into arguments, not branches","When looping, derive the plan: t.plan(cases.length)","Update t.plan in the same edit that adds or removes an assertion"],"tags":["node-test","assertions","test-plan"],"backgroundTag":"assertion-count-mismatch","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}