denoland/deno · error · TypeError

ERR_INVALID_RETURN_VALUE

ERR_INVALID_RETURN_VALUE

Error message

Expected instance of Promise to be returned from the "promiseFn" function but got ${resultPromise}.

What it means

Thrown by assert.rejects() and assert.doesNotReject() when the first argument is a function whose return value is not a Promise. waitForActual() invokes promiseFn() and immediately checks the result with checkIsPromise(); a plain value (including the undefined returned by a non-async function) fails that check, so the error is raised before any assertion logic runs.

Source

Thrown at ext/node/polyfills/assert.ts:353

  // Accept native ES6 promises and promises that are implemented in a similar
  // way. Do not accept thenables that use a function as `obj` and that have no
  // `catch` handler.
  return isPromise(obj) ||
    (obj !== null && typeof obj === "object" &&
      typeof obj.then === "function" &&
      typeof obj.catch === "function");
}

async function waitForActual(
  promiseFn,
) {
  let resultPromise;
  if (typeof promiseFn === "function") {
    // Return a rejected promise if `promiseFn` throws synchronously.
    resultPromise = promiseFn();
    // Fail in case no promise is returned.
    if (!checkIsPromise(resultPromise)) {
      throw new ERR_INVALID_RETURN_VALUE(
        "instance of Promise",
        "promiseFn",
        resultPromise,
      );
    }
  } else if (checkIsPromise(promiseFn)) {
    resultPromise = promiseFn;
  } else {
    throw new ERR_INVALID_ARG_TYPE(
      "promiseFn",
      ["Function", "Promise"],
      promiseFn,
    );
  }

  try {
    await resultPromise;
  } catch (e) {

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Make the callback async: assert.rejects(async () => { ... }) so it returns a rejected Promise
  2. Return a Promise explicitly from the callback (return Promise.reject(new Error(...)))
  3. Use assert.throws() instead when testing synchronous exceptions
  4. Pass a Promise directly as the first argument rather than a function

Example fix

// before
assert.rejects(() => { throw new Error('boom'); });
// after
assert.rejects(async () => { throw new Error('boom'); });
// or, for synchronous code
assert.throws(() => { throw new Error('boom'); });
Defensive patterns

Strategy: validation

Validate before calling

// Wrap the target so it always settles as a Promise
const settle = (fn) => async () => fn();
await assert.rejects(settle(syncFn), /boom/);

Type guard

const isPromise = (v) => v != null && typeof v.then === 'function' && typeof v.catch === 'function';

Try / catch

try { await assert.rejects(fn, /boom/); } catch (e) { if (e.code === 'ERR_INVALID_RETURN_VALUE') throw new Error('pass an async function or a Promise to assert.rejects'); throw e; }

Prevention

When it happens

Trigger: assert.rejects(() => { throw new Error('boom'); }) (the callback throws synchronously and returns undefined); assert.doesNotReject(() => 42); passing any non-async helper that returns a plain value where a promise-returning function is expected.

Common situations: Using the async assertion helpers on synchronous code; forgetting the async keyword on a test helper; refactoring a function from async to sync without updating assert.rejects call sites.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/bab939eafb329a3a. Report an issue: GitHub.