{"id":"e66aa44ae3212e36","repo":"vitest-dev/vitest","slug":"expected-promise-to-throw-an-error-but-it-didn-t","errorCode":null,"errorMessage":"expected promise to throw an error, but it didn't","messagePattern":"expected promise to throw an error, but it didn't","errorType":"exception","errorClass":"AssertionError","httpStatus":null,"severity":"error","filePath":"packages/expect/src/jest-expect.ts","lineNumber":90,"sourceCode":"        const object = utils.flag(this, 'object')\n        const isNot = utils.flag(this, 'negate') as boolean\n        if (promise === 'rejects') {\n          utils.flag(this, 'object', () => {\n            throw object\n          })\n        }\n        // if it got here, it's already resolved\n        // unless it tries to resolve to a function that should throw\n        // called as '.resolves[.not].toThrow()`\n        else if (promise === 'resolves' && typeof object !== 'function') {\n          if (!isNot) {\n            const message\n              = utils.flag(this, 'message')\n                || 'expected promise 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          else {\n            return\n          }\n        }\n        _super.apply(this, args)\n      }\n    })\n  })\n\n  // @ts-expect-error @internal\n  def('withTest', function (test: Test) {\n    utils.flag(this, 'vitest-test', test)\n    return this\n  })\n\n  def('toEqual', function (expected) {\n    const actual = utils.flag(this, 'object')","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/vitest-dev/vitest/blob/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/expect/src/jest-expect.ts#L72-L108","documentation":"Thrown by the 'throw'/'throws'/'Throw' assertion overwrite when a promise flagged as 'resolves' resolves to a value that is not a function. The library expects the resolved value to be a function that itself throws (the .resolves[.not].toThrow() pattern), and since a non-function cannot throw, the assertion fails immediately. This is distinct from the duplicate-looking line 774, which fires for the .toThrow(Error|errorObject) overload.","triggerScenarios":"Calling expect(promise).resolves.toThrow() or expect(promise).resolves.toThrow('string') or expect(promise).resolves.toThrow(/regex/) where the promise resolves to a non-function value (e.g. a number, object, or string). The code path is entered via the chai 'throws' overwrite (promise==='resolves' && typeof object !== 'function' && !isNot).","commonSituations":"Developers mistakenly use .resolves.toThrow() expecting it to verify that the promise itself rejects, when in fact .rejects.toThrow() is required. Also happens when refactoring a synchronous expect(fn).toThrow() into an async version and forgetting that the resolved value must be a throwing function.","solutions":["If you want to assert the promise rejects, use expect(promise).rejects.toThrow() instead of .resolves.toThrow().","If you genuinely expect the resolved value to be a function that throws, ensure the promise resolves to a function, e.g. expect(Promise.resolve(() => { throw new Error() })).resolves.toThrow().","Remove the .resolves modifier and assert on the synchronous throwing function directly: expect(fn).toThrow()."],"exampleFix":"// before\nawait expect(fetchData()).resolves.toThrow();\n// after (assert rejection)\nawait expect(fetchData()).rejects.toThrow();","handlingStrategy":"validation","validationCode":"const value = await promise;\nif (typeof value !== 'function') throw new TypeError('resolved value is not a function — use .rejects.toThrow() to assert rejection');","typeGuard":"const resolvesToThrowingFn = async (p: Promise<unknown>): p is Promise<() => never> =>\n  typeof (await p.catch(() => null)) === 'function';","tryCatchPattern":"try { await expect(promise).resolves.toThrow(); } catch (e) { if (/expected promise to throw/i.test(String(e))) { /* switch to .rejects */ } else throw e; }","preventionTips":["Prefer expect(promise).rejects.toThrow() for rejection assertions — .resolves.toThrow() is only for resolved throwing functions.","Document any test that intentionally uses .resolves.toThrow() so the intent is not 'fixed' later."],"tags":["assertion","async","promise","tothrow","resolves"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}