{"record":{"id":"8d4db10e6b1dbec5","repo":"denoland/deno","slug":"test-was-expected-to-fail-but-passed","errorCode":null,"errorMessage":"test was expected to fail but passed","messagePattern":"test was expected to fail but passed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ext/node/polyfills/testing.ts","lineNumber":1334,"sourceCode":"    }\n  }\n\n  let failed = false;\n  try {\n    await runWithTestGuards(\n      () => runNodeTestFunction(fn, nodeTestContext),\n      guards,\n    );\n    nodeTestContext._checkPlan();\n  } catch (err) {\n    failed = true;\n    assertExpectedFailure(err, options.expectFailure);\n  } finally {\n    await nodeTestContext._drainSubtests();\n  }\n\n  if (!failed) {\n    throw new Error(\"test was expected to fail but passed\");\n  }\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) {","sourceCodeStart":1316,"sourceCodeEnd":1352,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/node/polyfills/testing.ts#L1316-L1352","documentation":"A test registered with the expectFailure option inverts the pass/fail semantics: the runner expects the body to throw (runPossiblyExpectingFailure catches and, when expectFailure is a pattern/predicate, checks the error matches). If the body runs to completion without error, `failed` stays false and the runner throws 'test was expected to fail but passed'. So with this option, a passing test body is itself the failure.","triggerScenarios":"test('name', { expectFailure: true }, fn) where fn completes normally; the same with expectFailure: /pattern/ or a predicate, since a body that does not throw produces no error to match; async bodies whose rejection is accidentally swallowed (missing await, empty catch).","commonSituations":"The bug the test documented got fixed, so the body no longer throws but the expectFailure flag was never removed; TDD red tests left flagged after going green; refactors that wrapped the throwing call in a try/catch or optional chaining that now hides the error.","solutions":["If the underlying bug is fixed, remove the expectFailure option and assert success directly","If the test must still fail, make the body actually throw: assert.fail(), assert.throws(...), or await assert.rejects(...)","Audit the body for swallowed errors — missing await on async calls, .catch(() => {}), or try/catch around the formerly-throwing line","If expectFailure is a RegExp/string/predicate, confirm the intended failure actually matches it — and that it still occurs"],"exampleFix":"// before\ntest('parses bad input', { expectFailure: true }, () => {\n  parse('bad'); // bug was fixed; no longer throws\n});\n\n// after\ntest('parses bad input', () => {\n  assert.throws(() => parse('bad'), SyntaxError);\n});","handlingStrategy":"validation","validationCode":"// dev-time dry run: confirm the body actually throws before flagging expectFailure\nasync function expectFailTest(name, fn) {\n  let threw = false;\n  try {\n    await fn();\n  } catch {\n    threw = true;\n  }\n  if (!threw) {\n    throw new Error(`refusing to register expectFailure: '${name}' body does not throw`);\n  }\n  test(name, { expectFailure: true }, fn);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Remove expectFailure as part of the same change that fixes the bug it documents","Audit expectFailure bodies for swallowed errors (missing await, empty catch)","When expectFailure carries a pattern, also assert the error shape in a separate passing test"],"tags":["node-test","expect-failure","test-runner"],"backgroundTag":"expected-failure-test-passed","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"}