vitest-dev/vitest · error · AssertionError
expected promise to throw an error, but it didn't
Error message
expected promise to throw an error, but it didn't
What it means
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.
Source
Thrown at packages/expect/src/jest-expect.ts:90
const object = utils.flag(this, 'object')
const isNot = utils.flag(this, 'negate') as boolean
if (promise === 'rejects') {
utils.flag(this, 'object', () => {
throw object
})
}
// if it got here, it's already resolved
// unless it tries to resolve to a function that should throw
// called as '.resolves[.not].toThrow()`
else if (promise === 'resolves' && typeof object !== 'function') {
if (!isNot) {
const message
= utils.flag(this, 'message')
|| 'expected promise to throw an error, but it didn\'t'
const error = {
showDiff: false,
}
throw new AssertionError(message, error, utils.flag(this, 'ssfi'))
}
else {
return
}
}
_super.apply(this, args)
}
})
})
// @ts-expect-error @internal
def('withTest', function (test: Test) {
utils.flag(this, 'vitest-test', test)
return this
})
def('toEqual', function (expected) {
const actual = utils.flag(this, 'object')View on GitHub (pinned to d568f8ce37)
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().
Example fix
// before await expect(fetchData()).resolves.toThrow(); // after (assert rejection) await expect(fetchData()).rejects.toThrow();
Defensive patterns
Strategy: validation
Validate before calling
const value = await promise;
if (typeof value !== 'function') throw new TypeError('resolved value is not a function — use .rejects.toThrow() to assert rejection'); Type guard
const resolvesToThrowingFn = async (p: Promise<unknown>): p is Promise<() => never> => typeof (await p.catch(() => null)) === 'function';
Try / catch
try { await expect(promise).resolves.toThrow(); } catch (e) { if (/expected promise to throw/i.test(String(e))) { /* switch to .rejects */ } else throw e; } Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- You must provide a Promise to expect() when using .resolves,
- expected function to throw an error, but it didn't
- You must provide a Promise to expect() when using .rejects,
- You must provide an array or set to ${matcherHint('.toBeOneO
- Expected is not a string
AI-assisted analysis of vitest-dev/vitest@d568f8ce37 (2026-08-03).
Data as JSON: /data/errors/e66aa44ae3212e36.json.
Report an issue: GitHub.