{"record":{"id":"cb6c79569ce29233","repo":"awslabs/llrt","slug":"expected-function-to-throw-an-error-but-it-didn-t","errorCode":null,"errorMessage":"expected function to throw an error, but it didn't","messagePattern":"expected function to throw an error, but it didn't","errorType":"exception","errorClass":"AssertionError","httpStatus":null,"severity":"error","filePath":"llrt_core/src/modules/js/@llrt/expect/jest-expect.ts","lineNumber":467,"sourceCode":"          return;\n        }\n      } else {\n        let isThrow = false;\n        try {\n          obj();\n        } catch (err) {\n          isThrow = true;\n          thrown = err;\n        }\n\n        if (!isThrow && !isNot) {\n          const message =\n            utils.flag(this, \"message\") ||\n            \"expected function to throw an error, but it didn't\";\n          const error = {\n            showDiff: false,\n          };\n          throw new AssertionError(message, error, utils.flag(this, \"ssfi\"));\n        }\n      }\n\n      if (typeof expected === \"function\") {\n        // @ts-ignore\n        const name = expected.name || expected.prototype.constructor.name;\n        return this.assert(\n          thrown && thrown instanceof expected,\n          `expected error to be instance of ${name}`,\n          `expected error not to be instance of ${name}`,\n          expected,\n          thrown\n        );\n      }\n\n      if (expected instanceof Error) {\n        return this.assert(\n          thrown && expected.message === thrown.message,","sourceCodeStart":449,"sourceCodeEnd":485,"githubUrl":"https://github.com/awslabs/llrt/blob/742fc00b82cbeaab1c1b76f0d706c302a5cbc306/llrt_core/src/modules/js/@llrt/expect/jest-expect.ts#L449-L485","documentation":"The synchronous path of toThrow throws this AssertionError when the supplied function executes without throwing any error, and no specific expected error was matched (expected not a string/regex/error). It is the sync counterpart of the promise variants in [7]/[8].","triggerScenarios":"expect(() => fn()).toThrow() where fn() completes normally; also expect(fn).toThrow() with a function reference that doesn't throw when invoked.","commonSituations":"Bug was fixed so the function no longer throws; guard clauses return early instead of throwing; input that should be invalid is silently coerced; test passed an already-invoked result instead of a thunk (e.g. expect(fn()).toThrow() with a resolved value).","solutions":["Make the function throw for the invalid case: add `throw new Error('...')` where appropriate.","Pass a function/thunk to expect, not the invoked result: expect(() => fn()).toThrow().","Verify the inputs actually trigger the throwing branch (e.g. truly invalid arguments).","Update the test if non-throwing behavior is now correct, using toBe/toEqual instead."],"exampleFix":"// before\nfunction parse(n) { return Number(n); } // never throws\nexpect(() => parse('x')).toThrow();\n// after\nfunction parse(n) { const v = Number(n); if (Number.isNaN(v)) throw new Error('invalid number'); return v; }\nexpect(() => parse('x')).toThrow();","handlingStrategy":"try-catch","validationCode":"// sanity-check the function throws before asserting\nlet threw = false; try { fn(); } catch { threw = true; } if (!threw) throw new Error('fn must throw for these inputs');","typeGuard":null,"tryCatchPattern":"try { expect(() => fn()).toThrow(ExpectedError); } catch (e) { /* fn did not throw — fix fn or the inputs */ }","preventionTips":["Pass a thunk (() => fn()) to expect, never fn() result","Make invalid inputs actually trigger throw branches","Keep guard-clause refactors in sync with tests","Assert a specific error class/message to catch silent behavior drift"],"tags":["testing","assertion","sync"],"backgroundTag":"expected-throw-missing","analyzedSha":"742fc00b82cbeaab1c1b76f0d706c302a5cbc306","analyzedAt":"2026-09-12T11:14:07.838Z","contentChangedAt":"2026-09-12T11:14:07.838Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}