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 in the toThrow/throws/Throw overwrite when the assertion is in 'resolves' mode (expect(p).resolves.toThrow()), the awaited value is not a function, and the assertion is not negated. It means: the promise resolved successfully to a non-throwing value, so there was nothing to throw.

Solutions

  1. Use await expect(promise).rejects.toThrow() when you expect the promise itself to reject.
  2. If the promise resolves to a function and you want that function's throw, ensure the resolved value is actually a function.
  3. Re-check the assertion intent — most often .rejects is what was meant.

Example fix

// before
await expect(fetchUser()).resolves.toThrow()
// after
await expect(fetchUser()).rejects.toThrow('not found')
Defensive patterns

Strategy: validation

Validate before calling

// Clarify intent before writing the assertion.
function expectRejection(promise) {
  return expect(promise).rejects
}
// then: await expectRejection(fetchUser()).toThrow('not found')

Try / catch

try {
  await expect(promise).resolves.toThrow()
} catch (e) {
  if (/expected promise to throw/.test(String(e?.message))) {
    // likely meant .rejects — retry against rejection
    await expect(promise).rejects.toThrow()
    return
  }
  throw e
}

Prevention

When it happens

Trigger: await expect(promise).resolves.toThrow() where the promise resolves to a plain value (not a function) and .not is not present. The resolves branch expects a function that throws when called; a resolved value has nothing to invoke.

Common situations: Confusing resolves.toThrow with rejects.toThrow — wanting the promise to reject, not the resolved value to throw; calling toThrow on a promise that resolves to data expecting it to validate errors.

Related errors


AI-assisted analysis of vitest-dev/vitest@1fa9837ec2 (2026-08-11). Data as JSON: /api/errors/e66aa44ae3212e36. Report an issue: GitHub.

Appendix: 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 1fa9837ec2)