jestjs/jest · error · JestAssertionError

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

Error message

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

What it means

expect(actual).resolves.<matcher>() requires `actual` to be a Promise (or a function returning one) so it can await it before running the matcher. makeResolveMatcher throws a JestAssertionError if, after unwrapping, the value is not a promise. This catches the common mistake of chaining .resolves onto a synchronous value.

Solutions

  1. Ensure the value passed to expect() is a Promise: `expect(asyncFn()).resolves.toBe(x)` — call the function so it returns a promise.
  2. Drop `.resolves` and assert directly if the value is synchronous: `expect(syncFn()).toBe(x)`.
  3. If passing a function, make sure it returns a Promise: `expect(() => fetch(url)).resolves.toBe(...)`.

Example fix

// before
expect(getValue).resolves.toBe(42); // getValue is sync, not a promise/function-returning-promise

// after (if getValue is async)
expect(getValue()).resolves.toBe(42);
// after (if getValue is sync)
expect(getValue()).toBe(42);
Defensive patterns

Strategy: type-guard

Validate before calling

function isThenable(v) {
  return v != null && (typeof v === 'object' || typeof v === 'function') && typeof v.then === 'function';
}

// Only chain .resolves when actually thenable
const probe = typeof actual === 'function' ? actual() : actual;
if (!isThenable(probe)) {
  throw new TypeError('value is not a promise; remove .resolves');
}

Type guard

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

Prevention

When it happens

Trigger: Writing `expect(value).resolves.toBe(x)` where value is not a promise (e.g. a plain number, object, or a function that does not return a promise). The check runs before any matcher dispatch on the .resolves branch.

Common situations: Forgetting to await the function call whose result is asserted; testing a sync function with .resolves; a function whose async branch was not taken so it returned undefined; refactoring an async function to sync without updating tests.

Related errors


AI-assisted analysis of jestjs/jest@8e6d128e4a (2026-08-10). Data as JSON: /api/errors/852eef1578891dc3. Report an issue: GitHub.

Appendix: 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 8e6d128e4a)