jestjs/jest · error · JestAssertionError

received value must be a promise or a function returning a p

Error message

received value must be a promise or a function returning a promise

What it means

expect(x).resolves.<matcher>() wraps the actual value (or calls it if a function) and checks isPromise. If the value is not a Promise (and calling it didn't return one), it throws a JestAssertionError with a matcherHint header. .resolves only makes sense for promises; using it on a sync value is a misuse the library refuses to silently await.

Source

Thrown at packages/expect/src/index.ts:181

const makeResolveMatcher =
  (
    matcherName: string,
    matcher: RawMatcherFn,
    isNot: boolean,
    actual: Promise<any> | (() => Promise<any>),
    outerErr: JestAssertionError,
  ): PromiseMatcherFn =>
  (...args) => {
    const options = {
      isNot,
      promise: 'resolves',
    };

    const actualWrapper: Promise<any> =
      typeof actual === 'function' ? actual() : actual;

    if (!isPromise(actualWrapper)) {
      throw new JestAssertionError(
        matcherUtils.matcherErrorMessage(
          matcherUtils.matcherHint(matcherName, undefined, '', options),
          `${matcherUtils.RECEIVED_COLOR(
            'received',
          )} value must be a promise or a function returning a promise`,
          matcherUtils.printWithType(
            'Received',
            actual,
            matcherUtils.printReceived,
          ),
        ),
      );
    }

    const innerErr = new JestAssertionError();

    return actualWrapper.then(
      result =>

View on GitHub (pinned to f49721c78e)

Solutions

  1. Use plain expect for synchronous values: expect(value).toBe(5).
  2. Ensure the value is actually a Promise: expect(asyncFn()).resolves.toBe(5) — call the async function so it returns a promise.
  3. Add `await` where needed so you pass the unwrapped value to a sync matcher, or keep .resolves and pass the promise itself.

Example fix

// before
function getAnswer() { return 42; }
expect(getAnswer()).resolves.toBe(42); // not a promise

// after — sync matcher
expect(getAnswer()).toBe(42);
// or, if it should be async:
async function getAnswer() { return 42; }
expect(getAnswer()).resolves.toBe(42);
Defensive patterns

Strategy: type-guard

Validate before calling

const value = typeof actual === 'function' ? actual() : actual;
if (!isPromise(value)) {
  throw new TypeError('use plain expect() for non-promise values');
}
// now safe to use .resolves

Type guard

function isThenable(x: unknown): x is Promise<unknown> {
  return !!x && typeof (x as any).then === 'function';
}

Try / catch

// .resolves throws synchronously before returning the chained matcher — convert to a sync matcher or fix the source

Prevention

When it happens

Trigger: Calling expect(5).resolves.toBe(5), expect('foo').resolves.toEqual('foo'), expect(() => 42).resolves.toBe(42) (function returns a non-promise), or expect(maybePromise).resolves... where maybePromise resolved to a non-promise value.

Common situations: Forgetting to await the function before expect; testing a sync function with .resolves; refactoring an async function to sync without removing .resolves; double-awaiting.

Related errors


AI-assisted analysis of jestjs/jest@f49721c78e (2026-08-03). Data as JSON: /data/errors/852eef1578891dc3.json. Report an issue: GitHub.